Compare commits
30 Commits
10.11.114.
...
10.12.17.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cf098be85 | ||
|
|
c93c80468c | ||
|
|
a464594885 | ||
|
|
b42f8afa35 | ||
|
|
facf8bd401 | ||
|
|
feeb17eca3 | ||
|
|
b3405cd674 | ||
|
|
c35f9cef93 | ||
|
|
3f382202db | ||
|
|
2a3493cc82 | ||
|
|
aaa459ebe0 | ||
|
|
51a8acbc3e | ||
|
|
dc132a1999 | ||
|
|
0c31cfcbc5 | ||
|
|
0e44bc67cd | ||
|
|
12dfbba42c | ||
|
|
96b4287f3a | ||
|
|
7d406de29f | ||
|
|
81f0ef466a | ||
|
|
3f2d6b133c | ||
|
|
e776dc67eb | ||
|
|
bc5827d140 | ||
|
|
21838bf4af | ||
|
|
6090108597 | ||
|
|
b47b9e6f43 | ||
|
|
18d1cffb2d | ||
|
|
516fd7f235 | ||
|
|
2ee16c3533 | ||
|
|
7d22f5c78e | ||
|
|
3e604ee2fd |
14
README.md
14
README.md
@@ -1,11 +1,18 @@
|
||||
# ThingsGateway
|
||||
|
||||
|
||||
[](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)
|
||||
[](https://github.com/ThingsGateway/ThingsGateway)
|
||||
[](https://deepwiki.com/ThingsGateway/ThingsGateway)
|
||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
|
||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
|
||||
[](https://thingsgateway.cn/docs/1)
|
||||
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569">
|
||||
<img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ">
|
||||
</a>
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
A cross-platform, high-performance edge data collection gateway based on net9.
|
||||
A cross-platform, high-performance edge data collection gateway based on net8/10.
|
||||
|
||||
|
||||
## Documentation
|
||||
@@ -29,7 +36,6 @@ Account: **SuperAdmin**
|
||||
Password: **111111**
|
||||
|
||||
|
||||
**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**
|
||||
|
||||
## Docker
|
||||
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
# ThingsGateway
|
||||
|
||||
[](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)
|
||||
[](https://github.com/ThingsGateway/ThingsGateway)
|
||||
[](https://deepwiki.com/ThingsGateway/ThingsGateway)
|
||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
|
||||
[](https://www.nuget.org/packages/ThingsGateway.Foundation/)
|
||||
[](https://thingsgateway.cn/docs/1)
|
||||
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569">
|
||||
<img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ">
|
||||
</a>
|
||||
|
||||
## 介绍
|
||||
|
||||
基于net9的跨平台高性能边缘采集网关
|
||||
基于net8/10的跨平台高性能边缘采集网关
|
||||
|
||||
## 文档
|
||||
|
||||
@@ -19,7 +29,6 @@
|
||||
|
||||
密码 : **111111**
|
||||
|
||||
**右上角个人弹出框中,切换到物联网关模块**
|
||||
|
||||
## Docker
|
||||
|
||||
|
||||
@@ -8,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);
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="9.0.10" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
"SetDefaultModule": "Set as default module"
|
||||
},
|
||||
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
|
||||
"GCMemoryInfoConfig": "GCMemoryInfoConfig(MB)",
|
||||
"GCMemoryInfo": "GCMemoryInfo(MB)",
|
||||
"GCAnalyzeInfo": "GCAnalyzeInfo",
|
||||
"AvailableDisk": "Available Disk",
|
||||
"AvailableMemory": "Available Memory",
|
||||
"CpuUsage": "CPU Usage",
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
"SetDefaultModule": "设置为默认模块"
|
||||
},
|
||||
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
|
||||
"GCMemoryInfoConfig": "GC配置信息(MB)",
|
||||
"GCMemoryInfo": "GC内存信息(MB)",
|
||||
"GCAnalyzeInfo": "GC统计",
|
||||
|
||||
"AvailableDisk": "可用磁盘",
|
||||
"AvailableMemory": "可用内存",
|
||||
"CpuUsage": "CPU使用率",
|
||||
|
||||
@@ -5,131 +5,189 @@
|
||||
@using ThingsGateway.Admin.Application
|
||||
@namespace ThingsGateway.Admin.Razor
|
||||
<div class="row g-2 mx-1 form-inline">
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-12">
|
||||
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["SystemInfo"]
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<EditorFormObject IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow="1" RowType=RowType.Inline LabelWidth="160">
|
||||
<FieldItems>
|
||||
<EditorItem @bind-Field="@context.MachineInfo">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-12">
|
||||
<Display @bind-Value="@value.MachineInfo.OSName" DisplayText="@Localizer[nameof(value.MachineInfo.OSName)]" ShowTooltip ShowLabel=true>
|
||||
</Display>
|
||||
</div>
|
||||
<div class="col-12 col-md-12">
|
||||
<Display @bind-Value="@value.MachineInfo.OSVersion" DisplayText="@Localizer[nameof(value.MachineInfo.OSVersion)]" ShowTooltip ShowLabel=true>
|
||||
</Display>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.UUID">
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-12">
|
||||
<Display @bind-Value="@value.UUID" ShowTooltip ShowLabel=true>
|
||||
</Display>
|
||||
</div>
|
||||
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
<EditorItem @bind-Field="@context.DriveInfo" Ignore=true>
|
||||
</EditorItem>
|
||||
</FieldItems>
|
||||
</EditorFormObject>
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</div>
|
||||
<div class="col-12 col-md-8">
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["HardwareInfo"]
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<div class="d-flex align-items-center justify-content-center w-100 h-100" >
|
||||
|
||||
<div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100">
|
||||
<div class="col-12 col-md-4 justify-content-center h-100" >
|
||||
@{
|
||||
var item = HardwareJob.HardwareInfo.MachineInfo.CpuRate;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(item*100<=100?item*100:100))
|
||||
Color=@((item*100>70?Color.Warning:Color.Success))
|
||||
StrokeWidth="4" ShowProgress=false>
|
||||
<div class="circle-hardware">
|
||||
<span>
|
||||
@Localizer["CpuUsage"] <i> @((item * 100).ToString("F0") + " %")</i>
|
||||
</span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span>@(HardwareJob.HardwareInfo.MachineInfo.Processor)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var availableMemory = HardwareJob.HardwareInfo.MachineInfo.AvailableMemory;
|
||||
var memory = HardwareJob.HardwareInfo.MachineInfo.Memory;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center ">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100))
|
||||
Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success))
|
||||
StrokeWidth="4">
|
||||
<div class="circle-hardware">
|
||||
<h6> @((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %") </h6>
|
||||
|
||||
<span> @Localizer["WorkingSet"] <i> @(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span>
|
||||
<span> @Localizer["AvailableMemory"] <i> @((availableMemory / 1024.00 / 1024).ToString("F2") + " GB")</i></span>
|
||||
<span> @Localizer["TotalMemory"] <i> @((memory / 1024.00 / 1024).ToString("F2") + " GB")</i></span>
|
||||
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span> @Localizer["MemoryUsage"] </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace;
|
||||
var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center ">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100))
|
||||
Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success))
|
||||
StrokeWidth="4">
|
||||
<div class="circle-hardware">
|
||||
<h6> @((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6>
|
||||
<span> @(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel) </span>
|
||||
<span> @Localizer["AvailableDisk"] <i> @((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
|
||||
<span> @Localizer["TotalDisk"] <i> @((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span> @Localizer["DiskUsage"] </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center w-100">
|
||||
<span>@Localizer["SystemInfo"]</span>
|
||||
<small class="text-muted">
|
||||
@HardwareJob.HardwareInfo.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
</small>
|
||||
</div>
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<EditorForm class="mt-3" AutoGenerateAllItem="false" IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow=3 RowType=RowType.Inline LabelWidth=300>
|
||||
<FieldItems>
|
||||
|
||||
<EditorItem @bind-Field="@context.OSName" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.OsArchitecture" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.OSVersion" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.Environment" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.FrameworkDescription" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.UUID" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.SystemRunTotalMinute" GroupName="@Localizer["SystemInfo"]" />
|
||||
<EditorItem @bind-Field="@context.AppRunTotalMinute" GroupName="@Localizer["SystemInfo"]" />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.Memory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
|
||||
<EditorItem @bind-Field="@context.TotalAvailableMemory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
|
||||
<EditorItem @bind-Field="@context.HighMemoryLoadThreshold" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2 />
|
||||
|
||||
<EditorItem TValue="bool" TModel="HardwareInfo" @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2>
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6></h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
|
||||
<EditorItem @bind-Field="@context.GCLatencyMode" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.WorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.PrivateMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.PeakWorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3>
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6></h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
<EditorItem @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.TotalMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.FragmentedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.CommittedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
|
||||
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3>
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6></h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.GCAvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
<EditorItem @bind-Field="@context.VirtualMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
|
||||
|
||||
|
||||
<EditorItem @bind-Field="@context.GcGen0Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
<EditorItem @bind-Field="@context.GcGen1Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
<EditorItem @bind-Field="@context.GcGen2Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4>
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12">
|
||||
<h6></h6>
|
||||
</div>
|
||||
</EditTemplate>
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
<EditorItem @bind-Field="@context.TotalPauseDurationMs" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
|
||||
|
||||
</FieldItems>
|
||||
</EditorForm>
|
||||
|
||||
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-12">
|
||||
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
|
||||
<HeaderTemplate>
|
||||
@Localizer["HardwareInfo"]
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<div class="d-flex align-items-center justify-content-center w-100 h-100">
|
||||
<div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100">
|
||||
<!-- ✅ CPU 使用率 -->
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var cpuUsage = HardwareJob.HardwareInfo.CpuRate;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(cpuUsage*100<=100?cpuUsage*100:100))
|
||||
Color=@((cpuUsage*100>70?Color.Warning:Color.Success))
|
||||
StrokeWidth="4" ShowProgress=false>
|
||||
<div class="circle-hardware">
|
||||
<span>
|
||||
@Localizer["CpuUsage"] <i>@((cpuUsage * 100).ToString("F0") + " %")</i>
|
||||
</span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span>@(HardwareJob.HardwareInfo.Processor)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ 内存使用率 -->
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var availableMemory = HardwareJob.HardwareInfo.AvailableMemory;
|
||||
var memory = HardwareJob.HardwareInfo.Memory;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100))
|
||||
Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success))
|
||||
StrokeWidth="4">
|
||||
<div class="circle-hardware">
|
||||
<h6>@((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")</h6>
|
||||
<span>@Localizer["WorkingSet"] <i>@(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span>
|
||||
<span>@Localizer["AvailableMemory"] <i>@((availableMemory / 1024.00).ToString("F2") + " GB")</i></span>
|
||||
<span>@Localizer["TotalMemory"] <i>@((memory / 1024.00).ToString("F2") + " GB")</i></span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span>@Localizer["MemoryUsage"]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ 磁盘使用率 -->
|
||||
<div class="col-12 col-md-4 justify-content-center h-100">
|
||||
@{
|
||||
var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace;
|
||||
var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize;
|
||||
}
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<Circle Width="200" class="m-3"
|
||||
Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100))
|
||||
Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success))
|
||||
StrokeWidth="4">
|
||||
<div class="circle-hardware">
|
||||
<h6>@((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6>
|
||||
<span>@(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel)</span>
|
||||
<span>@Localizer["AvailableDisk"] <i>@((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
|
||||
<span>@Localizer["TotalDisk"] <i>@((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
|
||||
</div>
|
||||
</Circle>
|
||||
<div class="mt-1">
|
||||
<span>@Localizer["DiskUsage"]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-2 mx-1 form-inline">
|
||||
|
||||
<div class="col-12 col-md-12">
|
||||
@@ -140,7 +198,7 @@
|
||||
</HeaderTemplate>
|
||||
|
||||
<BodyTemplate>
|
||||
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" />
|
||||
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="() => { chartInit = true; return Task.CompletedTask; }" />
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -87,42 +87,45 @@ public class Startup : AppStartup
|
||||
#if NET8_0_OR_GREATER
|
||||
services
|
||||
.AddRazorComponents(options => options.TemporaryRedirectionUrlValidityDuration = TimeSpan.FromMinutes(10))
|
||||
.AddInteractiveServerComponents(options =>
|
||||
{
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
|
||||
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
|
||||
options.StreamBufferCapacity = 30;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
||||
});
|
||||
.AddInteractiveServerComponents(options =>
|
||||
{
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 5;
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
|
||||
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
|
||||
options.StreamBufferCapacity = 30;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
|
||||
});
|
||||
|
||||
#else
|
||||
|
||||
services.AddServerSideBlazor(options =>
|
||||
{
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
}).AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
|
||||
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
|
||||
options.StreamBufferCapacity = 30;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
||||
});
|
||||
{
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 5;
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
|
||||
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
|
||||
options.StreamBufferCapacity = 30;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
|
||||
});
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -15,15 +15,11 @@
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
<CETCompat>false</CETCompat>
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using ThingsGateway.Common.Extension;
|
||||
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.Collections.Frozen;
|
||||
#endif
|
||||
@@ -27,7 +28,7 @@ internal class CacheManager
|
||||
{
|
||||
private IMemoryCache Cache { get; set; }
|
||||
|
||||
private IServiceProvider Provider { get; set; }
|
||||
private static IServiceProvider Provider => App.RootServices;
|
||||
|
||||
[NotNull]
|
||||
private static CacheManager? Instance { get; set; }
|
||||
@@ -40,8 +41,7 @@ internal class CacheManager
|
||||
static CacheManager()
|
||||
{
|
||||
Instance = new();
|
||||
Instance.Provider = App.RootServices;
|
||||
Instance.Cache = Instance.Provider.GetRequiredService<IMemoryCache>();
|
||||
Instance.Cache = Provider.GetRequiredService<IMemoryCache>();
|
||||
Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value;
|
||||
}
|
||||
|
||||
@@ -105,37 +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>
|
||||
@@ -236,7 +205,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic
|
||||
? null
|
||||
: Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
|
||||
: Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 <see cref="JsonLocalizationOptions"/> 值
|
||||
@@ -244,7 +213,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
private static JsonLocalizationOptions GetJsonLocalizationOption()
|
||||
{
|
||||
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
|
||||
var localizationOptions = Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
|
||||
return localizationOptions.Value;
|
||||
}
|
||||
/// <summary>
|
||||
@@ -253,7 +222,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
private static BootstrapBlazorOptions GetBootstrapBlazorOption()
|
||||
{
|
||||
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
|
||||
var localizationOptions = Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
|
||||
return localizationOptions.Value;
|
||||
}
|
||||
/// <summary>
|
||||
@@ -269,7 +238,7 @@ internal class CacheManager
|
||||
return null;
|
||||
}
|
||||
IStringLocalizer? ret = null;
|
||||
var factories = Instance.Provider.GetServices<IStringLocalizerFactory>();
|
||||
var factories = Provider.GetServices<IStringLocalizerFactory>();
|
||||
var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
|
||||
if (factory != null)
|
||||
{
|
||||
@@ -287,8 +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>
|
||||
/// 通过指定程序集获取所有本地化信息键值集合
|
||||
@@ -299,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)
|
||||
{
|
||||
@@ -309,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 =>
|
||||
@@ -336,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) => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
|
||||
public static FrozenDictionary<string, string>? GetHasValueJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
|
||||
{
|
||||
if (assembly.IsDynamic)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
cultureName ??= CultureInfo.CurrentUICulture.Name;
|
||||
if (string.IsNullOrEmpty(cultureName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
|
||||
|
||||
|
||||
var typeKey = $"{CacheKeyPrefix}-{nameof(GetHasValueJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}";
|
||||
if (forceLoad)
|
||||
{
|
||||
Instance.Cache.Remove(key);
|
||||
Instance.Cache.Remove(typeKey);
|
||||
}
|
||||
|
||||
var localizedItems = Instance.GetOrCreate(key, entry =>
|
||||
{
|
||||
var sections = option.GetJsonStringFromAssembly(assembly, cultureName);
|
||||
var items = sections.SelectMany(section => section.GetChildren().Select(kv =>
|
||||
{
|
||||
var value = kv.Value;
|
||||
if (value == null && option.UseKeyWhenValueIsNull == true)
|
||||
{
|
||||
value = kv.Key;
|
||||
}
|
||||
return new LocalizedString(kv.Key, value ?? "", false, section.Key);
|
||||
}));
|
||||
#if NET8_0_OR_GREATER
|
||||
return items.ToFrozenSet();
|
||||
#else
|
||||
return items.ToHashSet();
|
||||
#endif
|
||||
});
|
||||
|
||||
var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry =>
|
||||
{
|
||||
return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase) && !item.ResourceNotFound).ToFrozenDictionary(a => a.Name, a => a.Value);
|
||||
});
|
||||
return typeLocalizedItems;
|
||||
}
|
||||
///// <summary>
|
||||
///// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
|
||||
///// </summary>
|
||||
///// <param name="typeName"></param>
|
||||
///// <param name="includeParentCultures"></param>
|
||||
///// <returns></returns>
|
||||
//public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
|
||||
#endregion
|
||||
|
||||
#region DisplayName
|
||||
|
||||
@@ -81,50 +81,16 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
/// <returns></returns>
|
||||
private string? GetStringSafely(string name) => GetStringFromJson(name);
|
||||
|
||||
private string? GetStringFromService(string name)
|
||||
{
|
||||
// get string from inject service
|
||||
string? ret = null;
|
||||
if (jsonLocalizationOptions.DisableGetLocalizerFromService == false)
|
||||
{
|
||||
var localizer = Utility.GetStringLocalizerFromService(Assembly, typeName);
|
||||
if (localizer != null && localizer is not JsonStringLocalizer)
|
||||
{
|
||||
var l = localizer[name];
|
||||
if (!l.ResourceNotFound)
|
||||
{
|
||||
ret = l.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private string? GetStringFromResourceManager(string name)
|
||||
{
|
||||
string? ret = null;
|
||||
if (jsonLocalizationOptions.DisableGetLocalizerFromResourceManager == false)
|
||||
{
|
||||
ret = GetStringSafely(name, CultureInfo.CurrentUICulture);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private readonly ConcurrentHashSet<string> _missingManifestCache = [];
|
||||
private string? GetStringFromJson(string name)
|
||||
{
|
||||
// get string from json localization file
|
||||
var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(Assembly, typeName));
|
||||
var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
|
||||
string? ret = null;
|
||||
if (!_missingManifestCache.Contain(cacheKey))
|
||||
{
|
||||
var l = localizerStrings.Find(i => i.Name == name);
|
||||
if (l is { ResourceNotFound: false })
|
||||
{
|
||||
ret = l.Value;
|
||||
}
|
||||
else
|
||||
var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(Assembly, typeName);
|
||||
if (localizerStrings?.TryGetValue(name, out ret) != true)
|
||||
{
|
||||
// 如果没有找到资源信息则尝试从父类中查找
|
||||
ret ??= GetStringFromBaseType(name);
|
||||
@@ -150,28 +116,13 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
if (baseType != type)
|
||||
{
|
||||
var baseAssembly = baseType.Assembly;
|
||||
var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(baseAssembly, baseType.FullName!));
|
||||
var l = localizerStrings.Find(i => i.Name == name);
|
||||
if (l is { ResourceNotFound: false })
|
||||
{
|
||||
ret = l.Value;
|
||||
}
|
||||
var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(baseAssembly, baseType.FullName!);
|
||||
_ = localizerStrings?.TryGetValue(name, out ret);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<LocalizedString> MegerResolveLocalizers(IEnumerable<LocalizedString>? localizerStrings)
|
||||
{
|
||||
var localizers = new List<LocalizedString>(CacheManager.GetTypeStringsFromResolve(typeName));
|
||||
|
||||
if (localizerStrings != null)
|
||||
{
|
||||
localizers.AddRange(localizerStrings);
|
||||
}
|
||||
return localizers;
|
||||
}
|
||||
|
||||
private void HandleMissingResourceItem(string name)
|
||||
{
|
||||
localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
@@ -183,7 +134,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
_missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}");
|
||||
}
|
||||
|
||||
private List<LocalizedString>? _allLocalizerdStrings;
|
||||
private LocalizedString[]? _allLocalizerdStrings;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前语言的所有资源信息
|
||||
@@ -198,7 +149,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
?? GetAllStringsFromBase()
|
||||
?? GetAllStringsFromJson();
|
||||
|
||||
_allLocalizerdStrings = MegerResolveLocalizers(items);
|
||||
_allLocalizerdStrings = items.ToArray();
|
||||
}
|
||||
return _allLocalizerdStrings;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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,20 +22,9 @@ 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;
|
||||
|
||||
/// <summary>最小个数。默认1</summary>
|
||||
public Int32 Min { get; set; } = 1;
|
||||
|
||||
private readonly object _syncRoot = new();
|
||||
|
||||
/// <summary>基础空闲集合。只保存最小个数,最热部分</summary>
|
||||
private readonly Stack<T> _free = new();
|
||||
|
||||
/// <summary>扩展空闲集合。保存最小个数以外部分</summary>
|
||||
private readonly Queue<T> _free2 = new();
|
||||
|
||||
/// <summary>借出去的放在这</summary>
|
||||
private readonly HashSet<T> _busy = new();
|
||||
|
||||
@@ -79,7 +68,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
if (_inited) return;
|
||||
_inited = true;
|
||||
|
||||
WriteLog($"Init {typeof(T).FullName} Min={Min} Max={Max}");
|
||||
WriteLog($"Init {typeof(T).FullName}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -92,33 +81,20 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
T? pi = null;
|
||||
do
|
||||
{
|
||||
lock (_syncRoot)
|
||||
lock (lockThis)
|
||||
{
|
||||
if (_free.Count > 0)
|
||||
{
|
||||
pi = _free.Pop();
|
||||
_FreeCount--;
|
||||
}
|
||||
else if (_free2.Count > 0)
|
||||
{
|
||||
pi = _free2.Dequeue();
|
||||
_FreeCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = BusyCount;
|
||||
if (Max > 0 && count >= Max)
|
||||
{
|
||||
var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}";
|
||||
WriteLog("Acquire Max " + msg);
|
||||
throw new Exception(Name + " " + msg);
|
||||
}
|
||||
|
||||
pi = OnCreate();
|
||||
if (count == 0) Init();
|
||||
if (BusyCount == 0) Init();
|
||||
|
||||
#if DEBUG
|
||||
WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, count + 1);
|
||||
WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, BusyCount + 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -126,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);
|
||||
@@ -141,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))
|
||||
@@ -175,12 +147,9 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lock (_syncRoot)
|
||||
lock (lockThis)
|
||||
{
|
||||
if (_FreeCount < Min)
|
||||
_free.Push(value);
|
||||
else
|
||||
_free2.Enqueue(value);
|
||||
_free.Push(value);
|
||||
_FreeCount++;
|
||||
}
|
||||
|
||||
@@ -195,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)
|
||||
{
|
||||
@@ -214,12 +174,6 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
OnDispose(pi);
|
||||
}
|
||||
|
||||
while (_free2.Count > 0)
|
||||
{
|
||||
var pi = _free2.Dequeue();
|
||||
OnDispose(pi);
|
||||
}
|
||||
|
||||
_FreeCount = 0;
|
||||
|
||||
foreach (var item in _busy)
|
||||
@@ -228,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();
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -61,7 +61,8 @@ public class TextFileLog : Logger, IDisposable
|
||||
MaxBytes = set.LogFileMaxBytes;
|
||||
Backups = set.LogFileBackups;
|
||||
|
||||
_Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000) { Async = true };
|
||||
_Timer = new TimerX(DoWriteAndClose, null, 0_000, 60_000, nameof(TextFileLog)) { Async = true };
|
||||
_WriteTimer = new TimerX(DoWrite, null, 0_000, 1000, nameof(TextFileLog)) { Async = true };
|
||||
}
|
||||
|
||||
private static readonly NonBlockingDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -96,6 +97,7 @@ public class TextFileLog : Logger, IDisposable
|
||||
protected virtual void Dispose(Boolean disposing)
|
||||
{
|
||||
_Timer.TryDispose();
|
||||
_WriteTimer.TryDispose();
|
||||
|
||||
// 销毁前把队列日志输出
|
||||
if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) WriteAndClose(DateTime.MinValue);
|
||||
@@ -147,35 +149,72 @@ public class TextFileLog : Logger, IDisposable
|
||||
|
||||
/// <summary>获取日志文件路径</summary>
|
||||
/// <returns></returns>
|
||||
private String? GetLogFile()
|
||||
private string? GetLogFile()
|
||||
{
|
||||
// 单日志文件
|
||||
if (_isFile) return LogPath.GetBasePath();
|
||||
|
||||
Directory.CreateDirectory(LogPath);
|
||||
// 目录多日志文件
|
||||
var logfile = LogPath.CombinePath(String.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)).GetBasePath();
|
||||
var baseFile = LogPath.CombinePath(
|
||||
string.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)
|
||||
).GetBasePath();
|
||||
|
||||
// 是否限制文件大小
|
||||
if (MaxBytes == 0) return logfile;
|
||||
// 不限制大小
|
||||
if (MaxBytes == 0) return baseFile;
|
||||
|
||||
// 找到今天第一个未达到最大上限的文件
|
||||
var max = MaxBytes * 1024L * 1024L;
|
||||
var ext = Path.GetExtension(logfile);
|
||||
var name = logfile.TrimEnd(ext);
|
||||
var ext = Path.GetExtension(FileFormat);
|
||||
|
||||
string? latestFile = null;
|
||||
DateTime latestTime = DateTime.MinValue;
|
||||
|
||||
foreach (var path in Directory.EnumerateFiles(LogPath, $"*{ext}", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
try
|
||||
{
|
||||
var lastWrite = File.GetLastWriteTimeUtc(path);
|
||||
if (lastWrite > latestTime)
|
||||
{
|
||||
latestTime = lastWrite;
|
||||
latestFile = path;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (latestFile != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var len = new FileInfo(latestFile).Length;
|
||||
if (len < max)
|
||||
return latestFile;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
var fileNameWithoutExt = Path.Combine(
|
||||
Path.GetDirectoryName(baseFile)!,
|
||||
Path.GetFileNameWithoutExtension(baseFile)
|
||||
);
|
||||
|
||||
// 依序找下一个可用文件
|
||||
for (var i = 1; i < 1024; i++)
|
||||
{
|
||||
if (i > 1) logfile = $"{name}_{i}{ext}";
|
||||
var nextFile = i == 1 ? $"{fileNameWithoutExt}{ext}" : $"{fileNameWithoutExt}_{i}{ext}";
|
||||
if (!File.Exists(nextFile))
|
||||
return nextFile;
|
||||
|
||||
var fi = logfile.AsFile();
|
||||
if (!fi.Exists || fi.Length < max) return logfile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 异步写日志
|
||||
private readonly TimerX? _Timer;
|
||||
private readonly TimerX? _WriteTimer;
|
||||
private readonly ConcurrentQueue<String> _Logs = new();
|
||||
private volatile Int32 _logCount;
|
||||
private Int32 _writing;
|
||||
@@ -186,9 +225,9 @@ public class TextFileLog : Logger, IDisposable
|
||||
{
|
||||
var writer = LogWriter;
|
||||
|
||||
var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours);
|
||||
var logFile = GetLogFile();
|
||||
if (logFile.IsNullOrEmpty()) return;
|
||||
var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours);
|
||||
|
||||
if (!_isFile && logFile != CurrentLogFile)
|
||||
{
|
||||
@@ -223,7 +262,10 @@ public class TextFileLog : Logger, IDisposable
|
||||
// 连续5秒没日志,就关闭
|
||||
_NextClose = now.AddSeconds(5);
|
||||
}
|
||||
|
||||
private void DoWrite(Object? state)
|
||||
{
|
||||
WriteLog();
|
||||
}
|
||||
/// <summary>关闭文件</summary>
|
||||
private void DoWriteAndClose(Object? state)
|
||||
{
|
||||
@@ -237,43 +279,36 @@ public class TextFileLog : Logger, IDisposable
|
||||
DirectoryInfo? di = new DirectoryInfo(LogPath);
|
||||
if (di.Exists)
|
||||
{
|
||||
// 删除*.del
|
||||
// 删除 *.del
|
||||
try
|
||||
{
|
||||
var dels = di.GetFiles("*.del");
|
||||
if (dels?.Length > 0)
|
||||
foreach (var item in di.EnumerateFiles("*.del"))
|
||||
{
|
||||
foreach (var item in dels)
|
||||
{
|
||||
item.Delete();
|
||||
}
|
||||
item.Delete();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
var ext = Path.GetExtension(FileFormat);
|
||||
var fis = di.GetFiles("*" + ext);
|
||||
if (fis != null && fis.Length > Backups)
|
||||
var fis = di.EnumerateFiles($"*{ext}")
|
||||
.OrderByDescending(e => e.LastWriteTimeUtc)
|
||||
.Skip(Backups);
|
||||
|
||||
foreach (var item in fis)
|
||||
{
|
||||
// 删除最旧的文件
|
||||
var retain = fis.Length - Backups;
|
||||
fis = fis.OrderBy(e => e.LastWriteTimeUtc).Take(retain).ToArray();
|
||||
foreach (var item in fis)
|
||||
OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length);
|
||||
try
|
||||
{
|
||||
item.Delete();
|
||||
}
|
||||
catch
|
||||
{
|
||||
OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length);
|
||||
try
|
||||
{
|
||||
item.Delete();
|
||||
item.MoveTo(item.FullName + ".del");
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
item.MoveTo(item.FullName + ".del");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,7 +358,6 @@ public class TextFileLog : Logger, IDisposable
|
||||
// 推入队列
|
||||
Enqueue($"{e.GetAndReset()}");
|
||||
|
||||
WriteLog();
|
||||
}
|
||||
|
||||
protected bool Check()
|
||||
@@ -340,35 +374,17 @@ public class TextFileLog : Logger, IDisposable
|
||||
}
|
||||
protected void WriteLog()
|
||||
{
|
||||
// 异步写日志,实时。即使这里错误,定时器那边仍然会补上
|
||||
// 写日志,实时。即使这里错误,定时器那边仍然会补上
|
||||
if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0)
|
||||
{
|
||||
// 调试级别 或 致命错误 同步写日志
|
||||
if (Setting.Current.LogLevel <= LogLevel.Debug || Level >= LogLevel.Error)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteFile();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writing = 0;
|
||||
}
|
||||
if (!_Logs.IsEmpty) WriteFile();
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
ThreadPool.UnsafeQueueUserWorkItem(s =>
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteFile();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_writing = 0;
|
||||
}
|
||||
}, null);
|
||||
_writing = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,13 +80,21 @@ public static class XTrace
|
||||
|
||||
Log.Error("{0}", ex);
|
||||
}
|
||||
public static void WriteException(Exception ex, string message)
|
||||
{
|
||||
if (!InitLog()) return;
|
||||
|
||||
WriteVersion();
|
||||
|
||||
Log.Error("{0}, {1}", message, ex);
|
||||
}
|
||||
#endregion 写日志
|
||||
|
||||
#region 构造
|
||||
|
||||
static XTrace()
|
||||
{
|
||||
_ = Runtime.AppTickCount64;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
|
||||
|
||||
@@ -12,18 +12,13 @@ namespace PooledAwait
|
||||
{
|
||||
private static ObjectPoolLock<T> pool = new();
|
||||
|
||||
[ThreadStatic]
|
||||
private static T? ts_local;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance from the pool if possible
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? TryGet()
|
||||
{
|
||||
var tmp = ts_local;
|
||||
ts_local = null;
|
||||
return tmp ?? pool.Get();
|
||||
return pool.Get();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -34,11 +29,6 @@ namespace PooledAwait
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
if (ts_local == null)
|
||||
{
|
||||
ts_local = value;
|
||||
return;
|
||||
}
|
||||
pool.Return(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,7 +580,19 @@ public static class Reflect
|
||||
|
||||
return func;
|
||||
}
|
||||
/// <summary>把一个方法转为泛型委托,便于快速反射调用</summary>
|
||||
/// <typeparam name="TFunc"></typeparam>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
public static void RemoveCache<TFunc>(this MethodInfo method, object? target = null) where TFunc : class
|
||||
{
|
||||
if (method == null) return;
|
||||
|
||||
var key = new DelegateCacheKey(method, typeof(TFunc), target);
|
||||
|
||||
DelegateCache<TFunc>.Cache.TryRemove(key);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
public static class DelegateCache<TFunc>
|
||||
|
||||
@@ -65,8 +65,21 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System;
|
||||
#endregion
|
||||
#region 构造
|
||||
private TimerScheduler(String name) => Name = name;
|
||||
|
||||
private TimerScheduler(String name)
|
||||
{
|
||||
Name = name;
|
||||
_processCallback = state =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Execute(state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
XTrace.WriteException(ex, "Timer执行错误");
|
||||
}
|
||||
};
|
||||
}
|
||||
/// <summary>销毁</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -191,6 +204,13 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
Count--;
|
||||
}
|
||||
}
|
||||
|
||||
timer.Method.RemoveCache<TimerCallback>(timer.Target.Target);
|
||||
#if NET6_0_OR_GREATER
|
||||
timer.Method.RemoveCache<Func<Object?, ValueTask>>(timer.Target.Target);
|
||||
#endif
|
||||
timer.Method.RemoveCache<Func<Object?, Task>>(timer.Target.Target);
|
||||
|
||||
}
|
||||
|
||||
private AutoResetEvent? _waitForTimer;
|
||||
@@ -251,17 +271,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
else
|
||||
//Task.Factory.StartNew(() => ProcessItem(timer));
|
||||
// 不需要上下文流动,捕获所有异常
|
||||
ThreadPool.UnsafeQueueUserWorkItem(s =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Execute(s);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
XTrace.WriteException(ex);
|
||||
}
|
||||
}, timer);
|
||||
ThreadPool.UnsafeQueueUserWorkItem(_processCallback, timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,7 +286,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
|
||||
WriteLog("调度线程已退出:{0}", Name);
|
||||
}
|
||||
|
||||
private readonly WaitCallback _processCallback;
|
||||
/// <summary>检查定时器是否到期</summary>
|
||||
/// <param name="timer"></param>
|
||||
/// <param name="now"></param>
|
||||
@@ -318,9 +328,9 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
|
||||
timer.hasSetNext = false;
|
||||
|
||||
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
string timerArg = timer.Timers.ToString();
|
||||
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
//string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
//string timerArg = timer.Timers.ToString();
|
||||
//using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
var sw = ValueStopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
@@ -344,7 +354,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
|
||||
catch (Exception ex)
|
||||
{
|
||||
span?.SetError(ex, null);
|
||||
//span?.SetError(ex, null);
|
||||
XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
@@ -370,9 +380,9 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
|
||||
timer.hasSetNext = false;
|
||||
|
||||
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
string timerArg = timer.Timers.ToString();
|
||||
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
//string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
//string timerArg = timer.Timers.ToString();
|
||||
//using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
var sw = ValueStopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
@@ -420,7 +430,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
|
||||
catch (Exception ex)
|
||||
{
|
||||
span?.SetError(ex, null);
|
||||
//span?.SetError(ex, null);
|
||||
XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -694,8 +694,12 @@ namespace ThingsGateway.SqlSugar
|
||||
var enumerator = table.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var cur = enumerator.Current;
|
||||
yield return cur.Value.Item2[rowIndex];
|
||||
var kvp = enumerator.Current;
|
||||
var list = kvp.Value.Item2;
|
||||
if (list != null && rowIndex < list.Count)
|
||||
yield return list[rowIndex];
|
||||
else
|
||||
yield return new DataInfos { ColumnName = kvp.Key, Value = DBNull.Value };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@ namespace ThingsGateway.SqlSugar
|
||||
{
|
||||
foreach (var column in columns)
|
||||
{
|
||||
|
||||
if (column.IsIgnore)
|
||||
{
|
||||
continue;
|
||||
@@ -210,6 +211,12 @@ namespace ThingsGateway.SqlSugar
|
||||
{
|
||||
name = column.PropertyName;
|
||||
}
|
||||
if (!results.TryGetValue(name, out var tuple) || tuple.Item2 == null)
|
||||
{
|
||||
// 某些列可能不在 DataTable 中(例如数据库多了列)
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = ValueConverter(column, GetValue(item, column));
|
||||
if (column.SqlParameterDbType != null && column.SqlParameterDbType is Type && UtilMethods.HasInterface((Type)column.SqlParameterDbType, typeof(ISugarDataConverter)))
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="9.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.11.114</PluginVersion>
|
||||
<ProPluginVersion>10.11.114</ProPluginVersion>
|
||||
<DefaultVersion>10.11.114</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
||||
<PluginVersion>10.12.17</PluginVersion>
|
||||
<ProPluginVersion>10.12.17</ProPluginVersion>
|
||||
<DefaultVersion>10.12.17</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.5</TSVersion>
|
||||
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -10,53 +10,53 @@
|
||||
<div class="w-100" style=@($"height:{HeightString}")>
|
||||
|
||||
<Card HeaderText=@HeaderText class=@("w-100 h-100")>
|
||||
<HeaderTemplate>
|
||||
<div class="flex-fill">
|
||||
</div>
|
||||
<HeaderTemplate>
|
||||
<div class="flex-fill">
|
||||
</div>
|
||||
|
||||
@if (LogLevelChanged.HasDelegate)
|
||||
{
|
||||
<Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select>
|
||||
}
|
||||
<Tooltip class=" col-auto" Title="@RazorLocalizer[Pause?"Play":"Pause"]" Placement="Placement.Bottom">
|
||||
@if (LogLevelChanged.HasDelegate)
|
||||
{
|
||||
<Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select>
|
||||
}
|
||||
<Tooltip class=" col-auto" Title=@(Pause? PlayText:PauseText) Placement="Placement.Bottom">
|
||||
|
||||
<Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause?"fa fa-play":"fa fa-pause") OnClick="OnPause" />
|
||||
<Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause ? "fa fa-play" : "fa fa-pause") OnClick="OnPause" />
|
||||
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip class=" col-auto" Title="@RazorLocalizer["Export"]" Placement="Placement.Bottom">
|
||||
<Tooltip class=" col-auto" Title="@ExportText" Placement="Placement.Bottom">
|
||||
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" />
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" />
|
||||
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip class=" col-auto" Title="@RazorLocalizer["Delete"]" Placement="Placement.Bottom">
|
||||
<Tooltip class=" col-auto" Title="@DeleteText" Placement="Placement.Bottom">
|
||||
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" />
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" />
|
||||
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
|
||||
|
||||
</HeaderTemplate>
|
||||
<BodyTemplate>
|
||||
<div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")>
|
||||
<Virtualize Items="CurrentMessages??new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2>
|
||||
<ItemContent>
|
||||
@* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@
|
||||
<div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"":
|
||||
itemMessage.Level>=(byte)Microsoft.Extensions.Logging.LogLevel.Warning? " red--text text-truncate":"green--text text-truncate")
|
||||
title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))>
|
||||
</HeaderTemplate>
|
||||
<BodyTemplate>
|
||||
<div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")>
|
||||
<Virtualize Items="CurrentMessages ?? new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2>
|
||||
<ItemContent>
|
||||
@* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@
|
||||
<div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"":
|
||||
itemMessage.Level >= (byte)Microsoft.Extensions.Logging.LogLevel.Warning ? " red--text text-truncate" : "green--text text-truncate")
|
||||
title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))>
|
||||
|
||||
@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150))
|
||||
@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150))
|
||||
|
||||
</div>
|
||||
@* </Tooltip> *@
|
||||
</ItemContent>
|
||||
</Virtualize>
|
||||
</div>
|
||||
</div>
|
||||
@* </Tooltip> *@
|
||||
</ItemContent>
|
||||
</Virtualize>
|
||||
</div>
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -10,12 +10,11 @@
|
||||
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
@@ -23,6 +22,24 @@ namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class LogConsole : IDisposable
|
||||
{
|
||||
|
||||
private string PlayText { get; set; }
|
||||
private string PauseText { get; set; }
|
||||
private string ExportText { get; set; }
|
||||
private string DeleteText { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
PlayText = RazorLocalizer["Play"];
|
||||
PauseText = RazorLocalizer["Pause"];
|
||||
ExportText = RazorLocalizer["Export"];
|
||||
DeleteText = RazorLocalizer["Delete"];
|
||||
|
||||
_Timer = new TimerX(RunTimerAsync, null, 1_000, 1_000, nameof(LogConsole)) { Async = true };
|
||||
base.OnInitialized();
|
||||
}
|
||||
private TimerX _Timer;
|
||||
|
||||
private bool Pause;
|
||||
|
||||
public bool Disposed { get; set; }
|
||||
@@ -69,7 +86,7 @@ public partial class LogConsole : IDisposable
|
||||
{
|
||||
logPath = LogPath;
|
||||
Messages = new List<LogMessage>();
|
||||
await ExecuteAsync();
|
||||
_Timer?.SetNext(0);
|
||||
}
|
||||
|
||||
await base.OnParametersSetAsync();
|
||||
@@ -82,63 +99,38 @@ public partial class LogConsole : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
_Timer?.SafeDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
private WaitLock WaitLock = new(nameof(LogConsole));
|
||||
protected async Task ExecuteAsync()
|
||||
protected async ValueTask ExecuteAsync()
|
||||
{
|
||||
if (WaitLock.Waited) return;
|
||||
try
|
||||
{
|
||||
await WaitLock.WaitAsync();
|
||||
await Task.Delay(1000);
|
||||
|
||||
if (LogPath != null)
|
||||
if (LogPath != null)
|
||||
{
|
||||
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
|
||||
if (!files.IsSuccess)
|
||||
{
|
||||
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
|
||||
if (!files.IsSuccess)
|
||||
Messages = new List<LogMessage>();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault());
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
await Task.Delay(1000);
|
||||
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault());
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
}
|
||||
sw.Stop();
|
||||
if (sw.ElapsedMilliseconds > 500)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
Messages = Array.Empty<LogMessage>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
WaitLock.Release();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = RunTimerAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
{
|
||||
@@ -185,19 +177,9 @@ public partial class LogConsole : IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunTimerAsync()
|
||||
private async Task RunTimerAsync(object? state)
|
||||
{
|
||||
while (!Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteAsync();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
}
|
||||
await ExecuteAsync();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using PooledAwait;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
@@ -26,65 +28,33 @@ public static class ChannelOptionsExtensions
|
||||
/// <param name="e">接收数据</param>
|
||||
/// <param name="funcs">事件</param>
|
||||
/// <returns></returns>
|
||||
internal static ValueTask OnChannelReceivedEvent(
|
||||
this IClientChannel clientChannel,
|
||||
ReceivedDataEventArgs e,
|
||||
ChannelReceivedEventHandler funcs)
|
||||
internal static ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
{
|
||||
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
||||
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
|
||||
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
|
||||
|
||||
if (funcs.Count == 0) return EasyValueTask.CompletedTask;
|
||||
return OnChannelReceivedEvent(clientChannel, e, funcs);
|
||||
|
||||
return InvokeHandlersSequentially(clientChannel, e, funcs);
|
||||
}
|
||||
|
||||
private static ValueTask InvokeHandlersSequentially(
|
||||
IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
{
|
||||
var enumerator = new HandlerEnumerator(clientChannel, e, funcs);
|
||||
return enumerator.MoveNextAsync();
|
||||
}
|
||||
|
||||
private struct HandlerEnumerator
|
||||
{
|
||||
private readonly IClientChannel _channel;
|
||||
private readonly ReceivedDataEventArgs _e;
|
||||
private readonly ChannelReceivedEventHandler _funcs;
|
||||
private int _index;
|
||||
|
||||
public HandlerEnumerator(IClientChannel channel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
static async PooledValueTask OnChannelReceivedEvent(IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
{
|
||||
_channel = channel;
|
||||
_e = e;
|
||||
_funcs = funcs;
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
public ValueTask MoveNextAsync()
|
||||
{
|
||||
_index++;
|
||||
if (_index >= _funcs.Count) return default;
|
||||
|
||||
var func = _funcs[_index];
|
||||
if (func == null) return MoveNextAsync();
|
||||
|
||||
bool isLast = _index == _funcs.Count - 1;
|
||||
var vt = func.Invoke(_channel, _e, isLast);
|
||||
if (vt.IsCompletedSuccessfully)
|
||||
if (funcs.Count > 0)
|
||||
{
|
||||
if (_e.Handled) return default;
|
||||
return MoveNextAsync();
|
||||
for (int i = 0; i < funcs.Count; i++)
|
||||
{
|
||||
var func = funcs[i];
|
||||
if (func == null) continue;
|
||||
var taskResult = func.Invoke(clientChannel, e, i == funcs.Count - 1);
|
||||
if (!taskResult.IsCompletedSuccessfully)
|
||||
{
|
||||
await taskResult.ConfigureAwait(false);
|
||||
}
|
||||
if (e.Handled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Awaited(vt);
|
||||
}
|
||||
|
||||
private async ValueTask Awaited(ValueTask vt)
|
||||
{
|
||||
await vt.ConfigureAwait(false);
|
||||
if (!_e.Handled)
|
||||
await MoveNextAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
|
||||
WaitHandlePool<MessageBase> WaitHandlePool { get; }
|
||||
|
||||
WaitLock GetLock(string key);
|
||||
void LogSeted(bool logSeted);
|
||||
|
||||
/// <summary>
|
||||
/// 设置数据处理适配器
|
||||
|
||||
@@ -80,13 +80,11 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
|
||||
|
||||
|
||||
private bool logSet;
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (!logSet && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
if (ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
logSet = true;
|
||||
handleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
@@ -96,12 +94,8 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
|
||||
logSet = false;
|
||||
}
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置数据处理适配器。
|
||||
/// </summary>
|
||||
|
||||
@@ -97,13 +97,15 @@ public static class PluginUtil
|
||||
{
|
||||
action += a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear()
|
||||
.SetCheckClearType(CheckClearType.All)
|
||||
.SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime))
|
||||
.SetOnClose((c, t) =>
|
||||
{
|
||||
return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout");
|
||||
});
|
||||
a.UseTcpSessionCheckClear(options =>
|
||||
{
|
||||
options.CheckClearType = CheckClearType.All;
|
||||
options.Tick = TimeSpan.FromMilliseconds(channelOptions.CheckClearTime);
|
||||
options.OnClose = (c, t) =>
|
||||
{
|
||||
return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout");
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
return action;
|
||||
|
||||
@@ -52,13 +52,11 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
|
||||
|
||||
private bool logSet;
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (!logSet && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
if (ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
logSet = true;
|
||||
handleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
@@ -68,13 +66,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
|
||||
logSet = false;
|
||||
}
|
||||
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -34,12 +34,10 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
}
|
||||
private bool logSet;
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
logSet = true;
|
||||
handleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
@@ -49,12 +47,8 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
|
||||
logSet = false;
|
||||
}
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
|
||||
|
||||
@@ -25,13 +25,11 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
public TcpSessionClientChannel()
|
||||
{
|
||||
}
|
||||
private bool logSet;
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
logSet = true;
|
||||
handleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
@@ -41,12 +39,8 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
|
||||
logSet = false;
|
||||
}
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
|
||||
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
|
||||
@@ -30,27 +30,21 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
ResetSign();
|
||||
}
|
||||
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
|
||||
private bool logSet;
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
logSet = true;
|
||||
handleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
public void LogSeted(bool logSeted)
|
||||
{
|
||||
logSet = logSeted;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
|
||||
SetAdapter(udpDataHandlingAdapter);
|
||||
|
||||
logSet = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1060,10 +1060,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
}
|
||||
|
||||
Channel.Collects.Remove(this);
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
{
|
||||
clientChannel.LogSeted(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1118,10 +1114,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
|
||||
Channel.Collects.Remove(this);
|
||||
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
{
|
||||
clientChannel.LogSeted(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,6 @@ public class TextFileLogger : ThingsGateway.NewLife.Log.TextFileLog, TouchSocket
|
||||
// 推入队列
|
||||
Enqueue(stringBuilder.ToString());
|
||||
|
||||
WriteLog();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -43,6 +43,16 @@ public struct OperResult<T> : IOperResult<T>
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从另一个操作对象中赋值信息
|
||||
/// </summary>
|
||||
public OperResult(OperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
@@ -129,8 +139,15 @@ public struct OperResult<T> : IOperResult<T>
|
||||
/// <param name="operResult"></param>
|
||||
public static implicit operator OperResult(OperResult<T> operResult)
|
||||
{
|
||||
return new OperResult(operResult);
|
||||
return new OperResult
|
||||
{
|
||||
OperCode = operResult.OperCode,
|
||||
ErrorMessage = operResult.ErrorMessage,
|
||||
Exception = operResult.Exception,
|
||||
ErrorType = operResult.ErrorType
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -162,7 +179,13 @@ public struct OperResult<T, T2> : IOperResult<T, T2>
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
|
||||
public OperResult(OperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
@@ -275,7 +298,13 @@ public struct OperResult<T, T2, T3> : IOperResult<T, T2, T3>
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
|
||||
public OperResult(OperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
@@ -389,7 +418,13 @@ public struct OperResult : IOperResult
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
|
||||
public OperResult(OperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -122,16 +122,11 @@ public class ControlController : ControllerBase, IRpcServer
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody][TouchSocket.WebApi.FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
{
|
||||
foreach (var deviceData in deviceDatas)
|
||||
{
|
||||
if (GlobalData.Devices.TryGetValue(deviceData.Key, out var device))
|
||||
{
|
||||
var data = device.VariableRuntimes.Where(a => deviceData.Value.ContainsKey(a.Key)).ToList();
|
||||
await GlobalData.SysUserService.CheckApiDataScopeAsync(data.Select(a => a.Value.CreateOrgId), data.Select(a => a.Value.CreateUserId)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
await GlobalData.CheckByDeviceNames(deviceDatas.Select(a => a.Key)).ConfigureAwait(false);
|
||||
|
||||
return (await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext?.GetRemoteIpAddressToIPv4()}", deviceDatas).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (OperResult)b.Value));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -380,7 +380,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
CancellationToken readToken = default;
|
||||
var readerLockTask = @this.ReadWriteLock.ReaderLockAsync(cancellationToken);
|
||||
if (!readerLockTask.IsCompleted)
|
||||
if (!readerLockTask.IsCompletedSuccessfully)
|
||||
{
|
||||
readToken = await readerLockTask.ConfigureAwait(false);
|
||||
}
|
||||
@@ -403,7 +403,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
|
||||
OperResult<ReadOnlyMemory<byte>> readResult = default;
|
||||
var readTask = @this.ReadSourceAsync(variableSourceRead, allToken);
|
||||
if (!readTask.IsCompleted)
|
||||
if (!readTask.IsCompletedSuccessfully)
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
@@ -430,12 +430,12 @@ 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));
|
||||
var readTask1 = @this.ReadSourceAsync(variableSourceRead, allToken);
|
||||
if (!readTask1.IsCompleted)
|
||||
if (!readTask1.IsCompletedSuccessfully)
|
||||
{
|
||||
readResult = await readTask1.ConfigureAwait(false);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,195 +490,6 @@ public abstract partial class CollectBase : DriverBase
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// private ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||
// {
|
||||
// var enumerator = new ReadVariableSourceEnumerator(this, state, cancellationToken);
|
||||
// return enumerator.MoveNextAsync();
|
||||
// }
|
||||
|
||||
// private struct ReadVariableSourceEnumerator
|
||||
// {
|
||||
// private readonly CollectBase _owner;
|
||||
// private readonly object? _state;
|
||||
// private readonly CancellationToken _cancellationToken;
|
||||
|
||||
// private VariableSourceRead _variableSourceRead;
|
||||
// private CancellationToken _readToken;
|
||||
// private CancellationToken _allToken;
|
||||
// private OperResult<ReadOnlyMemory<byte>> _readResult;
|
||||
// private int _readErrorCount;
|
||||
// private ValueTask<CancellationToken> _readerLockTask;
|
||||
// private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||
// private int _step;
|
||||
|
||||
// public ReadVariableSourceEnumerator(CollectBase owner, object? state, CancellationToken cancellationToken)
|
||||
// {
|
||||
// _owner = owner;
|
||||
// _state = state;
|
||||
// _cancellationToken = cancellationToken;
|
||||
|
||||
// _variableSourceRead = default!;
|
||||
// _readToken = default;
|
||||
// _allToken = default;
|
||||
// _readResult = default;
|
||||
// _readErrorCount = 0;
|
||||
// _readerLockTask = default;
|
||||
// _readTask = default;
|
||||
// _step = 0;
|
||||
// }
|
||||
|
||||
// public ValueTask MoveNextAsync()
|
||||
// {
|
||||
// switch (_step)
|
||||
// {
|
||||
// case 0:
|
||||
// if (_state is not VariableSourceRead vsr) return default;
|
||||
// _variableSourceRead = vsr;
|
||||
|
||||
// if (_owner.Pause) return default;
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readerLockTask = _owner.ReadWriteLock.ReaderLockAsync(_cancellationToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readerLockTask.IsCompleted)
|
||||
// {
|
||||
// _step = 1;
|
||||
// return AwaitReaderLock();
|
||||
// }
|
||||
// _readToken = _readerLockTask.Result;
|
||||
// goto case 2;
|
||||
|
||||
// case 1:
|
||||
// _readToken = _readerLockTask.Result;
|
||||
// goto case 2;
|
||||
|
||||
// case 2:
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// var allTokenSource = _owner._linkedCtsCache.GetLinkedTokenSource(_cancellationToken, _readToken);
|
||||
// _allToken = allTokenSource.Token;
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readTask.IsCompleted)
|
||||
// {
|
||||
// _step = 3;
|
||||
// return AwaitRead();
|
||||
// }
|
||||
// _readResult = _readTask.Result;
|
||||
// goto case 4;
|
||||
|
||||
// case 3:
|
||||
// _readResult = _readTask.Result;
|
||||
// goto case 4;
|
||||
|
||||
// case 4:
|
||||
// while (!_readResult.IsSuccess && _readErrorCount < _owner.CollectProperties.RetryCount)
|
||||
// {
|
||||
// if (_owner.Pause) return default;
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// _readErrorCount++;
|
||||
// if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readTask.IsCompleted)
|
||||
// {
|
||||
// _step = 5;
|
||||
// return AwaitReadRetry();
|
||||
// }
|
||||
// _readResult = _readTask.Result;
|
||||
// }
|
||||
|
||||
// goto case 6;
|
||||
|
||||
// case 5:
|
||||
// _readResult = _readTask.Result;
|
||||
// _step = 4;
|
||||
// return MoveNextAsync();
|
||||
|
||||
// case 6:
|
||||
// if (_readResult.IsSuccess)
|
||||
// {
|
||||
// if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.Content.Span.ToHexString(' ')));
|
||||
|
||||
// _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// if (_variableSourceRead.LastErrorMessage != _readResult.ErrorMessage)
|
||||
// {
|
||||
// if (!_cancellationToken.IsCancellationRequested)
|
||||
// _owner.LogMessage?.LogWarning(_readResult.Exception, string.Format(AppResource.CollectFail, _owner.DeviceName,
|
||||
// _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (!_cancellationToken.IsCancellationRequested && _owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
// }
|
||||
|
||||
// _variableSourceRead.LastErrorMessage = _readResult.ErrorMessage;
|
||||
// _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null, _readResult.ErrorMessage);
|
||||
// var time = DateTime.Now;
|
||||
// foreach (var item in _variableSourceRead.VariableRuntimes)
|
||||
// {
|
||||
// item.SetValue(null, time, isOnline: false);
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
|
||||
// return default;
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitReaderLock()
|
||||
// {
|
||||
// await _readerLockTask.ConfigureAwait(false);
|
||||
// _step = 1;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitRead()
|
||||
// {
|
||||
// await _readTask.ConfigureAwait(false);
|
||||
// _step = 3;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitReadRetry()
|
||||
// {
|
||||
// await _readTask.ConfigureAwait(false);
|
||||
// _step = 5;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -755,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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
// 从协议读取数据
|
||||
OperResult<ReadOnlyMemory<byte>> read = default;
|
||||
var readTask = @this.FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
||||
if (!readTask.IsCompleted)
|
||||
if (!readTask.IsCompletedSuccessfully)
|
||||
{
|
||||
read = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
@@ -200,116 +200,6 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
|
||||
///// </summary>
|
||||
// protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
// {
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>( new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException()));
|
||||
|
||||
// // 值类型状态机
|
||||
// var stateMachine = new ReadSourceStateMachine(this, variableSourceRead, cancellationToken);
|
||||
// return stateMachine.MoveNextAsync();
|
||||
// }
|
||||
|
||||
// private struct ReadSourceStateMachine
|
||||
// {
|
||||
// private readonly VariableSourceRead _variableSourceRead;
|
||||
// private readonly CancellationToken _cancellationToken;
|
||||
// private readonly CollectFoundationBase _owner;
|
||||
// private OperResult<ReadOnlyMemory<byte>> _result;
|
||||
// private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||
|
||||
// public ReadSourceStateMachine(CollectFoundationBase owner, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
// {
|
||||
// _owner = owner;
|
||||
// _variableSourceRead = variableSourceRead;
|
||||
// _cancellationToken = cancellationToken;
|
||||
// _result = default;
|
||||
// State = 0;
|
||||
// }
|
||||
|
||||
// public int State { get; private set; }
|
||||
|
||||
// public ValueTask<OperResult<ReadOnlyMemory<byte>>> MoveNextAsync()
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// switch (State)
|
||||
// {
|
||||
// case 0:
|
||||
// // 异步读取
|
||||
// if (_cancellationToken.IsCancellationRequested)
|
||||
// {
|
||||
// _result = new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException());
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
// }
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.FoundationDevice.ReadAsync(_variableSourceRead.AddressObject, _cancellationToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
|
||||
// // 检查是否任务已完成
|
||||
// if (_readTask.IsCompleted)
|
||||
// {
|
||||
// _result = _readTask.Result;
|
||||
// State = 1;
|
||||
// return MoveNextAsync();
|
||||
// }
|
||||
|
||||
// // 如果任务尚未完成,继续等待
|
||||
// State = 2;
|
||||
// return Awaited(_readTask);
|
||||
|
||||
// case 1:
|
||||
// // 解析结构化内容
|
||||
// if (_result.IsSuccess)
|
||||
// {
|
||||
// var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||
// }
|
||||
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
|
||||
// case 2:
|
||||
// // 完成任务后,解析内容
|
||||
// _result = _readTask.Result;
|
||||
|
||||
// if (_result.IsSuccess)
|
||||
// {
|
||||
// var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||
// }
|
||||
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
|
||||
// default:
|
||||
// throw new InvalidOperationException("Unexpected state.");
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(ex));
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async ValueTask<OperResult<ReadOnlyMemory<byte>>> Awaited(ValueTask<OperResult<ReadOnlyMemory<byte>>> vt)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
|
||||
|
||||
// await vt.ConfigureAwait(false);
|
||||
// return await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写
|
||||
/// </summary>
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
[SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)]
|
||||
[SugarIndex("unique_deviceid_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)]
|
||||
#endif
|
||||
public class Variable : BaseDataEntity, IValidatableObject
|
||||
public class Variable : PrimaryKeyEntity, IValidatableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键Id
|
||||
|
||||
@@ -15,6 +15,7 @@ using PooledAwait;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
@@ -109,11 +110,15 @@ public static class GlobalData
|
||||
static async PooledTask<IEnumerable<DeviceRuntime>> GetCurrentUserDevices()
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
return ReadOnlyIdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
return IdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<long> GetCurrentUserDeviceIds(HashSet<long> dataScope)
|
||||
{
|
||||
return IdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Key);
|
||||
}
|
||||
public static Task<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables()
|
||||
{
|
||||
return GetCurrentUserIdVariables();
|
||||
@@ -121,11 +126,53 @@ public static class GlobalData
|
||||
static async PooledTask<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables()
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
return IdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
|
||||
return IdVariables.Where(a => a.Value.IsInternalMemoryVariable == false)
|
||||
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.DeviceRuntime.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.DeviceRuntime.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task CheckByDeviceNames(IEnumerable<string> deviceNames)
|
||||
{
|
||||
List<long> orgids = new();
|
||||
List<long> userIds = new();
|
||||
foreach (var deviceData in GlobalData.Devices.FilterByKeys(deviceNames))
|
||||
{
|
||||
orgids.Add(deviceData.Value.CreateOrgId);
|
||||
userIds.Add(deviceData.Value.CreateUserId);
|
||||
}
|
||||
await GlobalData.SysUserService.CheckApiDataScopeAsync(orgids, userIds).ConfigureAwait(false);
|
||||
}
|
||||
public static async Task CheckByDeviceIds(IEnumerable<long> deviceIds)
|
||||
{
|
||||
List<long> orgids = new();
|
||||
List<long> userIds = new();
|
||||
foreach (var deviceData in GlobalData.IdDevices.FilterByKeys(deviceIds))
|
||||
{
|
||||
orgids.Add(deviceData.Value.CreateOrgId);
|
||||
userIds.Add(deviceData.Value.CreateUserId);
|
||||
}
|
||||
await GlobalData.SysUserService.CheckApiDataScopeAsync(orgids, userIds).ConfigureAwait(false);
|
||||
}
|
||||
public static async Task CheckByVariableIds(IEnumerable<long> variableIds)
|
||||
{
|
||||
List<long> orgids = new();
|
||||
List<long> userIds = new();
|
||||
foreach (var deviceData in GlobalData.IdVariables.FilterByKeys(variableIds))
|
||||
{
|
||||
orgids.Add(deviceData.Value.DeviceRuntime.CreateOrgId);
|
||||
userIds.Add(deviceData.Value.DeviceRuntime.CreateUserId);
|
||||
}
|
||||
await GlobalData.SysUserService.CheckApiDataScopeAsync(orgids, userIds).ConfigureAwait(false);
|
||||
}
|
||||
public static async Task CheckByVariableId(long variableId)
|
||||
{
|
||||
if (GlobalData.IdVariables.TryGetValue(variableId, out var variable))
|
||||
{
|
||||
await GlobalData.SysUserService.CheckApiDataScopeAsync(variable.DeviceRuntime.CreateOrgId, variable.DeviceRuntime.CreateUserId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public static Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync()
|
||||
{
|
||||
return GetCurrentUserRealAlarmVariablesAsync();
|
||||
@@ -133,7 +180,8 @@ public static class GlobalData
|
||||
static async PooledTask<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync()
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
return RealAlarmIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
return RealAlarmIdVariables
|
||||
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
}
|
||||
}
|
||||
@@ -145,8 +193,8 @@ public static class GlobalData
|
||||
static async PooledTask<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables()
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
return AlarmEnableIdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
return AlarmEnableIdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.DeviceRuntime.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.DeviceRuntime.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,9 +327,12 @@ public static class GlobalData
|
||||
{
|
||||
if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null)
|
||||
return true;
|
||||
else if (GlobalData.IdDevices.Any(a => a.Value.RedundantDeviceId == deviceRuntime.Id))
|
||||
|
||||
var id = deviceRuntime.Id;
|
||||
foreach (var kv in GlobalData.IdDevices)
|
||||
{
|
||||
return true;
|
||||
if (kv.Value.RedundantDeviceId == id)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -26,6 +26,8 @@ public static partial class GatewayMapper
|
||||
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.EventTime)}", nameof(AlarmVariable.EventTime))]
|
||||
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.AlarmType)}", nameof(AlarmVariable.AlarmType))]
|
||||
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.EventType)}", nameof(AlarmVariable.EventType))]
|
||||
[MapProperty($"{nameof(VariableRuntime.DeviceRuntime)}.{nameof(DeviceRuntime.CreateOrgId)}", nameof(AlarmVariable.CreateOrgId))]
|
||||
[MapProperty($"{nameof(VariableRuntime.DeviceRuntime)}.{nameof(DeviceRuntime.CreateUserId)}", nameof(AlarmVariable.CreateUserId))]
|
||||
public static partial AlarmVariable AdaptAlarmVariable(this VariableRuntime src);
|
||||
|
||||
public static partial VariableDataWithValue AdaptVariableDataWithValue(this VariableBasicData src);
|
||||
|
||||
@@ -66,11 +66,6 @@ public partial class VariableRuntime : Variable
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
|
||||
public DateTime CollectTime { get => collectTime; set => collectTime = value; }
|
||||
|
||||
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
|
||||
[AutoGenerateColumn(Visible = false, DefaultSort = false, Sortable = true)]
|
||||
[IgnoreExcel]
|
||||
public override int SortCode { get => sortCode; set => sortCode = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 上次值
|
||||
/// </summary>
|
||||
@@ -245,7 +240,6 @@ public partial class VariableRuntime : Variable
|
||||
|
||||
|
||||
private int index;
|
||||
private int sortCode;
|
||||
private DateTime changeTime = DateTime.UnixEpoch.ToLocalTime();
|
||||
|
||||
private DateTime collectTime = DateTime.UnixEpoch.ToLocalTime();
|
||||
|
||||
@@ -386,7 +386,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
ManageHelper.CheckChannelCount(insertData.Count);
|
||||
|
||||
using var db = GetDB();
|
||||
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
|
||||
if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
|
||||
{
|
||||
await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
|
||||
await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);
|
||||
|
||||
@@ -387,7 +387,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
|
||||
ManageHelper.CheckDeviceCount(insertData.Count);
|
||||
|
||||
using var db = GetDB();
|
||||
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
|
||||
if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
|
||||
{
|
||||
await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
|
||||
await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);
|
||||
|
||||
@@ -113,6 +113,28 @@ public partial class ManagementTask : AsyncDisposableObject
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear();
|
||||
|
||||
|
||||
a.UseReconnection<TcpDmtpClient>(options =>
|
||||
{
|
||||
options.TryCount = -1;
|
||||
options.PollingInterval = TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval);
|
||||
options.PrintLog = true;
|
||||
options.CheckAction = async (c, count) =>
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
if ((await c.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess)
|
||||
{
|
||||
return ConnectionCheckResult.Alive;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ConnectionCheckResult.Dead;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b =>
|
||||
{
|
||||
b.UseSystemTextJson(json =>
|
||||
@@ -131,10 +153,6 @@ public partial class ManagementTask : AsyncDisposableObject
|
||||
a.Add<FilePlugin>();
|
||||
|
||||
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
|
||||
.SetMaxFailCount(3);
|
||||
|
||||
a.AddDmtpCreatedChannelPlugin(async () =>
|
||||
{
|
||||
try
|
||||
@@ -194,9 +212,6 @@ public partial class ManagementTask : AsyncDisposableObject
|
||||
|
||||
a.Add<FilePlugin>();
|
||||
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
|
||||
.SetMaxFailCount(3);
|
||||
});
|
||||
|
||||
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);
|
||||
|
||||
@@ -63,7 +63,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
const int highMemorySize = 100000;
|
||||
const long memoryThreshold = 2L * 1024 * 1024; // 2GB,单位KB
|
||||
|
||||
return (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory > memoryThreshold && WebEnableVariable.WebEnable == true)
|
||||
return (GlobalData.HardwareJob.HardwareInfo.AvailableMemory > memoryThreshold && WebEnableVariable.WebEnable == true)
|
||||
? highMemorySize
|
||||
: defaultSize;
|
||||
}
|
||||
@@ -345,7 +345,25 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear();
|
||||
a.UseReconnection<TcpDmtpClient>(options =>
|
||||
{
|
||||
options.TryCount = -1;
|
||||
options.PollingInterval = TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval);
|
||||
options.PrintLog = true;
|
||||
options.CheckAction = async (c, count) =>
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
if ((await c.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess)
|
||||
{
|
||||
return ConnectionCheckResult.Alive;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ConnectionCheckResult.Dead;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b =>
|
||||
{
|
||||
b.UseSystemTextJson(json =>
|
||||
@@ -358,9 +376,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
json.Converters.Add(new JArraySystemTextJsonConverter());
|
||||
});
|
||||
}));
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||
.SetMaxFailCount(redundancy.MaxErrorCount);
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -405,9 +421,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
json.Converters.Add(new JArraySystemTextJsonConverter());
|
||||
});
|
||||
}));
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||
.SetMaxFailCount(redundancy.MaxErrorCount);
|
||||
|
||||
});
|
||||
|
||||
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ using System.Text;
|
||||
|
||||
using ThingsGateway.Common.Extension;
|
||||
using ThingsGateway.Common.Extension.Generic;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Foundation.Extension.Dynamic;
|
||||
|
||||
using TouchSocket.Core;
|
||||
@@ -107,8 +106,6 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
variable.DataType = DataTypeEnum.Int16;
|
||||
variable.Name = name;
|
||||
variable.Id = id;
|
||||
variable.CreateOrgId = UserManager.OrgId;
|
||||
variable.CreateUserId = UserManager.UserId;
|
||||
variable.DeviceId = device.Id;
|
||||
variable.RegisterAddress = address;
|
||||
newVariables.Add(variable);
|
||||
@@ -210,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);
|
||||
@@ -334,8 +331,6 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
variable.DataType = DataTypeEnum.Int16;
|
||||
variable.Name = name;
|
||||
variable.Id = id;
|
||||
variable.CreateOrgId = UserManager.OrgId;
|
||||
variable.CreateUserId = UserManager.UserId;
|
||||
variable.DeviceId = device.Id;
|
||||
variable.RegisterAddress = address;
|
||||
newVariables.Add(variable);
|
||||
@@ -347,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);
|
||||
@@ -428,12 +423,9 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
differences.Remove(nameof(Variable.VariablePropertys));
|
||||
if (differences?.Count > 0)
|
||||
{
|
||||
var data = models.ToList();
|
||||
await GlobalData.CheckByDeviceIds(data.Select(a => a.DeviceId)).ConfigureAwait(false);
|
||||
using var db = GetDB();
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
var data = models
|
||||
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
||||
.ToList();
|
||||
|
||||
var result = (await db.Updateable(data).UpdateColumns(differences.Select(a => a.Key).ToArray()).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
||||
|
||||
@@ -448,24 +440,20 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
[OperDesc("DeleteVariable", isRecordPar: false, localizerType: typeof(Variable))]
|
||||
public async Task DeleteByDeviceIdAsync(IEnumerable<long> input, SqlSugarClient db)
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
var ids = input.ToList();
|
||||
var result = await db.Deleteable<Variable>().Where(a => ids.Contains(a.DeviceId))
|
||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
||||
.ExecuteCommandAsync().ConfigureAwait(false);
|
||||
await GlobalData.CheckByDeviceIds(ids).ConfigureAwait(false);
|
||||
|
||||
var result = await db.Deleteable<Variable>().Where(a => ids.Contains(a.DeviceId))
|
||||
.ExecuteCommandAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[OperDesc("DeleteVariable", isRecordPar: false, localizerType: typeof(Variable))]
|
||||
public async Task<bool> DeleteVariableAsync(IEnumerable<long> input)
|
||||
{
|
||||
using var db = GetDB();
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
var ids = input?.ToList();
|
||||
await GlobalData.CheckByVariableIds(ids).ConfigureAwait(false);
|
||||
var result = (await db.Deleteable<Variable>().WhereIF(input != null, a => ids.Contains(a.Id))
|
||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
||||
.ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
||||
|
||||
return result;
|
||||
@@ -505,6 +493,11 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(GatewayExportFilter exportFilter)
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
List<long>? filterDeviceIds = null;
|
||||
if (dataScope != null)
|
||||
{
|
||||
filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||
}
|
||||
HashSet<long>? deviceId = null;
|
||||
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -520,8 +513,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
|
||||
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
|
||||
|
||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
||||
.WhereIF(filterDeviceIds != null, u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询
|
||||
|
||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()));
|
||||
return whereQuery;
|
||||
@@ -530,6 +522,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
private async Task<Func<IEnumerable<Variable>, IEnumerable<Variable>>> GetWhereEnumerableFunc(GatewayExportFilter exportFilter, bool sql = false)
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
List<long>? filterDeviceIds = null;
|
||||
if (dataScope != null)
|
||||
{
|
||||
filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||
}
|
||||
|
||||
|
||||
HashSet<long>? deviceId = null;
|
||||
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -545,8 +544,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
|
||||
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
|
||||
|
||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
||||
.WhereIF(filterDeviceIds != null, u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询
|
||||
|
||||
.WhereIF(sql && exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()))
|
||||
.WhereIF(!sql && exportFilter.PluginType == PluginTypeEnum.Business && exportFilter.DeviceId > 0, u =>
|
||||
@@ -566,7 +564,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
public async Task<bool> SaveVariableAsync(Variable input, ItemChangedType type)
|
||||
{
|
||||
if (type == ItemChangedType.Update)
|
||||
await GlobalData.SysUserService.CheckApiDataScopeAsync(input.CreateOrgId, input.CreateUserId).ConfigureAwait(false);
|
||||
await GlobalData.CheckByVariableId(input.Id).ConfigureAwait(false);
|
||||
else
|
||||
ManageHelper.CheckVariableCount(1);
|
||||
|
||||
@@ -622,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);
|
||||
//导出
|
||||
@@ -696,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);
|
||||
@@ -767,6 +765,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
|
||||
public ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, NonBlockingDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
|
||||
{
|
||||
|
||||
List<long>? filterDeviceIds = null;
|
||||
if (dataScope != null)
|
||||
{
|
||||
filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||
}
|
||||
|
||||
string ImportNullError = Localizer["ImportNullError"];
|
||||
string RedundantDeviceError = Localizer["RedundantDeviceError"];
|
||||
|
||||
@@ -839,17 +844,14 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
if (GlobalData.IdDevices.TryGetValue(variable.DeviceId, out var dbvar1s) && dbvar1s.VariableRuntimes.TryGetValue(variable.Name, out var dbvar1))
|
||||
{
|
||||
variable.Id = dbvar1.Id;
|
||||
variable.CreateOrgId = dbvar1.CreateOrgId;
|
||||
variable.CreateUserId = dbvar1.CreateUserId;
|
||||
variable.IsUp = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.IsUp = false;
|
||||
variable.CreateOrgId = UserManager.OrgId;
|
||||
variable.CreateUserId = UserManager.UserId;
|
||||
}
|
||||
if (device.IsUp && ((dataScope != null && dataScope?.Count > 0 && !dataScope.Contains(variable.CreateOrgId)) || dataScope?.Count == 0 && variable.CreateUserId != UserManager.UserId))
|
||||
|
||||
if (device.IsUp && (filterDeviceIds?.Contains(variable.DeviceId) != false))
|
||||
{
|
||||
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, "Operation not permitted"));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" />
|
||||
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />-->
|
||||
|
||||
@@ -1347,27 +1347,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
private TreeViewItem<ChannelDeviceTreeItem> UnknownTreeViewItem;
|
||||
|
||||
SmartTriggerScheduler? scheduler;
|
||||
private bool _initialized;
|
||||
public override async Task SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
parameters.SetParameterProperties(this);
|
||||
if (!_initialized)
|
||||
{
|
||||
_initialized = true;
|
||||
|
||||
OnInitialized();
|
||||
await OnInitializedAsync();
|
||||
OnParametersSet();
|
||||
StateHasChanged();
|
||||
await OnParametersSetAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnParametersSet();
|
||||
StateHasChanged();
|
||||
await OnParametersSetAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -1610,9 +1590,10 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
{
|
||||
|
||||
Disposed = true;
|
||||
ChannelRuntimeDispatchService.UnSubscribe(Refresh);
|
||||
ChannelRuntimeDispatchService?.UnSubscribe(Refresh);
|
||||
|
||||
await Module.InvokeVoidAsync("dispose", Id);
|
||||
if (Module != null)
|
||||
await Module.InvokeVoidAsync("dispose", Id);
|
||||
|
||||
await base.DisposeAsync(disposing);
|
||||
}
|
||||
|
||||
@@ -296,9 +296,10 @@ public partial class VariableRuntimeInfo
|
||||
{
|
||||
|
||||
Disposed = true;
|
||||
VariableRuntimeDispatchService.UnSubscribe(Refresh);
|
||||
VariableRuntimeDispatchService?.UnSubscribe(Refresh);
|
||||
|
||||
await Module.InvokeVoidAsync("dispose", Id);
|
||||
if (Module != null)
|
||||
await Module.InvokeVoidAsync("dispose", Id);
|
||||
|
||||
await base.DisposeAsync(disposing);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams\ThingsGateway.Blazor.Diagrams.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" />
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -7,15 +7,13 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
|
||||
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode>
|
||||
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,426 +0,0 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:https://touchsocket.net/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace BenchmarkConsoleApp;
|
||||
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class BenchmarkAsyncWaitData
|
||||
{
|
||||
private int Count = 100000;
|
||||
|
||||
[Benchmark]
|
||||
public async Task RunAsyncWaitDataPool()
|
||||
{
|
||||
var waitHandlePool = new WaitHandlePool<MyWaitData>();
|
||||
var cts = new CancellationTokenSource(1000 * 60);
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
var data = new MyWaitData();
|
||||
using (var waitData = waitHandlePool.GetWaitDataAsync(data))
|
||||
{
|
||||
var task = Task.Run(() =>
|
||||
{
|
||||
waitHandlePool.Set(data);
|
||||
});
|
||||
|
||||
|
||||
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
|
||||
await task;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task RunAsyncWaitData()
|
||||
{
|
||||
var waitHandlePool = new WaitHandlePool2<MyWaitData>();
|
||||
var cts = new CancellationTokenSource(1000 * 60);
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
var data = new MyWaitData();
|
||||
using (var waitData = waitHandlePool.GetWaitDataAsync(data))
|
||||
{
|
||||
var task = Task.Run(() =>
|
||||
{
|
||||
waitHandlePool.Set(data);
|
||||
});
|
||||
|
||||
|
||||
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
|
||||
await task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task RunAsyncWaitDataDelayPool()
|
||||
{
|
||||
var waitHandlePool = new WaitHandlePool<MyWaitData>();
|
||||
var cts = new CancellationTokenSource(1000 * 60);
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
var data = new MyWaitData();
|
||||
using (var waitData = waitHandlePool.GetWaitDataAsync(data))
|
||||
{
|
||||
var task = waitData.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
|
||||
waitData.Set(data);
|
||||
|
||||
await task;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task RunAsyncWaitDataDelay()
|
||||
{
|
||||
var waitHandlePool = new WaitHandlePool2<MyWaitData>();
|
||||
var cts = new CancellationTokenSource(1000 * 60);
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
var data = new MyWaitData();
|
||||
using (var waitData = waitHandlePool.GetWaitDataAsync(data))
|
||||
{
|
||||
var task = waitData.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
|
||||
waitData.Set(data);
|
||||
|
||||
await task;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class MyWaitData : IWaitHandle
|
||||
{
|
||||
public int Sign { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WaitHandlePool2<T>
|
||||
where T : class, IWaitHandle
|
||||
{
|
||||
private readonly int m_maxSign;
|
||||
private readonly int m_minSign;
|
||||
private readonly ConcurrentDictionary<int, AsyncWaitData2<T>> m_waitDic = new();
|
||||
private readonly Action<int> _remove;
|
||||
private int m_currentSign;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化<see cref="WaitHandlePool{T}"/>类的新实例。
|
||||
/// </summary>
|
||||
/// <param name="minSign">签名的最小值,默认为1。</param>
|
||||
/// <param name="maxSign">签名的最大值,默认为<see cref="int.MaxValue"/>。</param>
|
||||
/// <remarks>
|
||||
/// 签名范围用于控制自动生成的唯一标识符的取值范围。
|
||||
/// 当签名达到最大值时,会自动重置到最小值重新开始分配。
|
||||
/// </remarks>
|
||||
public WaitHandlePool2(int minSign = 1, int maxSign = int.MaxValue)
|
||||
{
|
||||
this.m_minSign = minSign;
|
||||
this.m_currentSign = minSign;
|
||||
this.m_maxSign = maxSign;
|
||||
|
||||
this._remove = this.Remove;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消池中所有等待操作。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 此方法会遍历池中所有的等待数据,并调用其<see cref="AsyncWaitData{T}.Cancel"/>方法来取消等待。
|
||||
/// 取消后的等待数据会从池中移除。适用于应用程序关闭或需要批量取消所有等待操作的场景。
|
||||
/// </remarks>
|
||||
public void CancelAll()
|
||||
{
|
||||
var signs = this.m_waitDic.Keys.ToList();
|
||||
foreach (var sign in signs)
|
||||
{
|
||||
if (this.m_waitDic.TryRemove(sign, out var item))
|
||||
{
|
||||
item.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取与指定结果关联的异步等待数据。
|
||||
/// </summary>
|
||||
/// <param name="result">要关联的结果对象。</param>
|
||||
/// <param name="autoSign">指示是否自动为结果对象分配签名,默认为<see langword="true"/>。</param>
|
||||
/// <returns>创建的<see cref="AsyncWaitData{T}"/>实例。</returns>
|
||||
/// <exception cref="InvalidOperationException">当指定的签名已被使用时抛出。</exception>
|
||||
/// <remarks>
|
||||
/// 如果<paramref name="autoSign"/>为<see langword="true"/>,方法会自动为结果对象生成唯一签名。
|
||||
/// 创建的等待数据会被添加到池中,直到被设置结果或取消时才会移除。
|
||||
/// </remarks>
|
||||
public AsyncWaitData2<T> GetWaitDataAsync(T result, bool autoSign = true)
|
||||
{
|
||||
if (autoSign)
|
||||
{
|
||||
result.Sign = this.GetSign();
|
||||
}
|
||||
var waitDataAsyncSlim = new AsyncWaitData2<T>(result.Sign, this._remove, result);
|
||||
|
||||
if (!this.m_waitDic.TryAdd(result.Sign, waitDataAsyncSlim))
|
||||
{
|
||||
//ThrowHelper.ThrowInvalidOperationException($"The sign '{result.Sign}' is already in use.");
|
||||
return default;
|
||||
}
|
||||
return waitDataAsyncSlim;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取具有自动生成签名的异步等待数据。
|
||||
/// </summary>
|
||||
/// <param name="sign">输出参数,返回自动生成的签名值。</param>
|
||||
/// <returns>创建的<see cref="AsyncWaitData{T}"/>实例。</returns>
|
||||
/// <exception cref="InvalidOperationException">当生成的签名已被使用时抛出。</exception>
|
||||
/// <remarks>
|
||||
/// 此方法会自动生成唯一签名,并创建不包含挂起数据的等待对象。
|
||||
/// 适用于只需要等待通知而不关心具体数据内容的场景。
|
||||
/// </remarks>
|
||||
public AsyncWaitData2<T> GetWaitDataAsync(out int sign)
|
||||
{
|
||||
sign = this.GetSign();
|
||||
var waitDataAsyncSlim = new AsyncWaitData2<T>(sign, this._remove, default);
|
||||
if (!this.m_waitDic.TryAdd(sign, waitDataAsyncSlim))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return waitDataAsyncSlim;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定结果设置对应签名的等待操作。
|
||||
/// </summary>
|
||||
/// <param name="result">包含签名和结果数据的对象。</param>
|
||||
/// <returns>如果成功设置等待操作则返回<see langword="true"/>;否则返回<see langword="false"/>。</returns>
|
||||
/// <remarks>
|
||||
/// 此方法根据结果对象的签名查找对应的等待数据,并设置其结果。
|
||||
/// 设置成功后,等待数据会从池中移除,正在等待的任务会被完成。
|
||||
/// 如果找不到对应签名的等待数据,则返回<see langword="false"/>。
|
||||
/// </remarks>
|
||||
public bool Set(T result)
|
||||
{
|
||||
if (this.m_waitDic.TryRemove(result.Sign, out var waitDataAsync))
|
||||
{
|
||||
waitDataAsync.Set(result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取指定签名的异步等待数据。
|
||||
/// </summary>
|
||||
/// <param name="sign">要查找的签名。</param>
|
||||
/// <param name="waitDataAsync">输出参数,如果找到则返回对应的等待数据;否则为<see langword="null"/>。</param>
|
||||
/// <returns>如果找到指定签名的等待数据则返回<see langword="true"/>;否则返回<see langword="false"/>。</returns>
|
||||
/// <remarks>
|
||||
/// 此方法允许查询池中是否存在特定签名的等待数据,而不会修改池的状态。
|
||||
/// 适用于需要检查等待状态或获取等待数据进行进一步操作的场景。
|
||||
/// </remarks>
|
||||
public bool TryGetDataAsync(int sign, out AsyncWaitData2<T> waitDataAsync)
|
||||
{
|
||||
return this.m_waitDic.TryGetValue(sign, out waitDataAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成下一个可用的唯一签名。
|
||||
/// </summary>
|
||||
/// <returns>生成的唯一签名值。</returns>
|
||||
/// <remarks>
|
||||
/// 使用原子递增操作确保签名的唯一性和线程安全性。
|
||||
/// 当签名达到最大值时,会重新开始分配以避免溢出。
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int GetSign()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var currentSign = this.m_currentSign;
|
||||
var nextSign = currentSign >= this.m_maxSign ? this.m_minSign : currentSign + 1;
|
||||
|
||||
if (Interlocked.CompareExchange(ref this.m_currentSign, nextSign, currentSign) == currentSign)
|
||||
{
|
||||
return nextSign;
|
||||
}
|
||||
// 如果CAS失败,继续重试
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从池中移除指定签名的等待数据。
|
||||
/// </summary>
|
||||
/// <param name="sign">要移除的签名。</param>
|
||||
/// <remarks>
|
||||
/// 此方法由等待数据在释放时自动调用,确保池中不会保留已完成或已取消的等待对象。
|
||||
/// </remarks>
|
||||
private void Remove(int sign)
|
||||
{
|
||||
this.m_waitDic.TryRemove(sign, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AsyncWaitData2<T> : DisposableObject, IValueTaskSource<WaitDataStatus>
|
||||
{
|
||||
// ManualResetValueTaskSourceCore 是一个结构体,避免了额外托管对象分配,但需要配合 token 使用。
|
||||
private ManualResetValueTaskSourceCore<T> _core; // 核心结构体,不会分配额外对象
|
||||
|
||||
// 缓存的移除回调,由 WaitHandlePool 构造时传入,避免每次分配委托。
|
||||
private readonly Action<int> _remove;
|
||||
|
||||
// 挂起时的临时数据
|
||||
private readonly T _pendingData;
|
||||
|
||||
// 完成时的数据
|
||||
private T _completedData;
|
||||
|
||||
// 当前等待状态(成功/取消/未完成等)
|
||||
private WaitDataStatus _status;
|
||||
private CancellationTokenRegistration Registration;
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定签名和移除回调初始化一个新的 <see cref="AsyncWaitData{T}"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="sign">此等待项对应的签名(用于在池中查找)。</param>
|
||||
/// <param name="remove">完成或释放时调用的回调,用于将此实例从等待池中移除。</param>
|
||||
/// <param name="pendingData">可选的挂起数据,当创建时可以携带一个初始占位数据。</param>
|
||||
public AsyncWaitData2(int sign, Action<int> remove, T pendingData)
|
||||
{
|
||||
this.Sign = sign;
|
||||
this._remove = remove;
|
||||
this._pendingData = pendingData;
|
||||
this._core.RunContinuationsAsynchronously = true; // 确保续体异步执行,避免潜在的栈内联执行问题
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取此等待项的签名标识。
|
||||
/// </summary>
|
||||
public int Sign { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取挂起时的原始数据(如果在创建时传入)。
|
||||
/// </summary>
|
||||
public T PendingData => this._pendingData;
|
||||
|
||||
/// <summary>
|
||||
/// 获取已完成时的返回数据。
|
||||
/// </summary>
|
||||
public T CompletedData => this._completedData;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前等待状态(例如:Success、Canceled 等)。
|
||||
/// </summary>
|
||||
public WaitDataStatus Status => this._status;
|
||||
|
||||
/// <summary>
|
||||
/// 取消当前等待,标记为已取消并触发等待任务的异常(OperationCanceledException)。
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
this.Set(WaitDataStatus.Canceled, default!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将等待项设置为成功并携带结果数据。
|
||||
/// </summary>
|
||||
/// <param name="result">要设置的完成数据。</param>
|
||||
public void Set(T result)
|
||||
{
|
||||
this.Set(WaitDataStatus.Success, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置等待项的状态和数据,并完成对应的 ValueTask。
|
||||
/// </summary>
|
||||
/// <param name="status">要设置的状态。</param>
|
||||
/// <param name="result">要设置的完成数据。</param>
|
||||
public void Set(WaitDataStatus status, T result)
|
||||
{
|
||||
this._status = status;
|
||||
this._completedData = result;
|
||||
|
||||
if (status == WaitDataStatus.Canceled)
|
||||
this._core.SetException(new OperationCanceledException());
|
||||
else
|
||||
this._core.SetResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待此项完成,返回一个 <see cref="ValueTask{WaitDataStatus}"/>,可传入取消令牌以取消等待。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">可选的取消令牌。若触发则会调用 <see cref="Cancel"/>。</param>
|
||||
/// <returns>表示等待状态的 ValueTask。</returns>
|
||||
public ValueTask<WaitDataStatus> WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
this.Registration = cancellationToken.Register(this.Cancel);
|
||||
}
|
||||
|
||||
return new ValueTask<WaitDataStatus>(this, this._core.Version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从核心获取结果(显式接口实现)。
|
||||
/// 注意:此方法由 ValueTask 基础设施调用,不应直接在用户代码中调用。
|
||||
/// </summary>
|
||||
WaitDataStatus IValueTaskSource<WaitDataStatus>.GetResult(short token)
|
||||
{
|
||||
this._core.GetResult(token);
|
||||
return this._status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前 ValueTask 源的状态(显式接口实现)。
|
||||
/// </summary>
|
||||
ValueTaskSourceStatus IValueTaskSource<WaitDataStatus>.GetStatus(short token)
|
||||
=> this._core.GetStatus(token);
|
||||
|
||||
/// <summary>
|
||||
/// 注册续体(显式接口实现)。
|
||||
/// 注意:flags 可以控制是否捕获上下文等行为。
|
||||
/// </summary>
|
||||
void IValueTaskSource<WaitDataStatus>.OnCompleted(Action<object?> continuation, object? state,
|
||||
short token, ValueTaskSourceOnCompletedFlags flags)
|
||||
=> this._core.OnCompleted(continuation, state, token, flags);
|
||||
|
||||
/// <summary>
|
||||
/// 释放托管资源时调用,会触发传入的移除回调,从所在的等待池中移除此等待项。
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否为显式释放。</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.Registration.Dispose();
|
||||
this._remove(this.Sign);
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,11 +11,6 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
|
||||
using Longbow.Modbus;
|
||||
using Longbow.TcpSocket;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using System.Net.Sockets;
|
||||
|
||||
using ThingsGateway.Foundation.Modbus;
|
||||
@@ -33,14 +28,12 @@ namespace ThingsGateway.Foundation;
|
||||
[MemoryDiagnoser]
|
||||
public class ModbusBenchmark : IDisposable
|
||||
{
|
||||
public static int ClientCount = 1;
|
||||
public static int ClientCount = 10;
|
||||
public static int TaskNumberOfItems = 1;
|
||||
public static int NumberOfItems = 10;
|
||||
public static int NumberOfItems = 100;
|
||||
|
||||
private readonly List<IModbusClient> _lgbModbusClients = [];
|
||||
private List<ModbusMaster> thingsgatewaymodbuss = new();
|
||||
private List<IModbusMaster> nmodbuss = new();
|
||||
//private List<ModbusTcpNet> modbusTcpNets = new();
|
||||
private List<ModbusTcpMaster> modbusTcpMasters = new();
|
||||
|
||||
[GlobalSetup]
|
||||
@@ -74,15 +67,7 @@ public class ModbusBenchmark : IDisposable
|
||||
await nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
|
||||
nmodbuss.Add(nmodbus);
|
||||
}
|
||||
//for (int i = 0; i < ClientCount; i++)
|
||||
//{
|
||||
// ModbusTcpNet modbusTcpNet = new();
|
||||
// modbusTcpNet.IpAddress = "127.0.0.1";
|
||||
// modbusTcpNet.Port = 502;
|
||||
// modbusTcpNet.ConnectServer();
|
||||
// modbusTcpNet.ReadAsync("0", 100);
|
||||
// modbusTcpNets.Add(modbusTcpNet);
|
||||
//}
|
||||
|
||||
|
||||
for (int i = 0; i < ClientCount; i++)
|
||||
{
|
||||
@@ -94,23 +79,6 @@ public class ModbusBenchmark : IDisposable
|
||||
modbusTcpMasters.Add(client);
|
||||
}
|
||||
|
||||
{
|
||||
var sc = new ServiceCollection();
|
||||
sc.AddTcpSocketFactory();
|
||||
sc.AddModbusFactory();
|
||||
|
||||
var provider = sc.BuildServiceProvider();
|
||||
var factory = provider.GetRequiredService<IModbusFactory>();
|
||||
|
||||
for (int i = 0; i < ClientCount; i++)
|
||||
{
|
||||
var client = factory.GetOrCreateTcpMaster();
|
||||
await client.ConnectAsync("127.0.0.1", 502);
|
||||
await client.ReadHoldingRegistersAsync(0x01, 0x00, 10);
|
||||
|
||||
_lgbModbusClients.Add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
@@ -140,33 +108,6 @@ public class ModbusBenchmark : IDisposable
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task LongbowModbus()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
foreach (var client in _lgbModbusClients)
|
||||
{
|
||||
|
||||
for (int i = 0; i < TaskNumberOfItems; i++)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
for (int i = 0; i < NumberOfItems; i++)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(3000);
|
||||
var result = await client.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false);
|
||||
var data = result.ReadUShortValues(100);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.Exception);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task TouchSocket()
|
||||
{
|
||||
@@ -214,39 +155,12 @@ public class ModbusBenchmark : IDisposable
|
||||
}
|
||||
|
||||
|
||||
//并发失败
|
||||
//[Benchmark]
|
||||
//public async Task HslCommunication()
|
||||
//{
|
||||
// List<Task> tasks = new List<Task>();
|
||||
// foreach (var modbusTcpNet in modbusTcpNets)
|
||||
// {
|
||||
// for (int i = 0; i < TaskNumberOfItems; i++)
|
||||
// {
|
||||
// tasks.Add(Task.Run(async () =>
|
||||
// {
|
||||
// for (int i = 0; i < NumberOfItems; i++)
|
||||
// {
|
||||
// var result = await modbusTcpNet.ReadAsync("0", 100);
|
||||
// if (!result.IsSuccess)
|
||||
// {
|
||||
// throw new Exception(result.Message);
|
||||
// }
|
||||
// }
|
||||
// }));
|
||||
// }
|
||||
// }
|
||||
// await Task.WhenAll(tasks);
|
||||
//}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
thingsgatewaymodbuss?.ForEach(a => a.Channel.SafeDispose());
|
||||
thingsgatewaymodbuss?.ForEach(a => a.SafeDispose());
|
||||
nmodbuss?.ForEach(a => a.SafeDispose());
|
||||
//modbusTcpNets?.ForEach(a => a.SafeDispose());
|
||||
_lgbModbusClients?.ForEach(a => a.DisposeAsync());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,8 +11,6 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
|
||||
using HslCommunication.Profinet.Siemens;
|
||||
|
||||
using S7.Net;
|
||||
|
||||
using ThingsGateway.Foundation.SiemensS7;
|
||||
@@ -33,7 +31,6 @@ public class S7Benchmark : IDisposable
|
||||
private List<SiemensS7Master> siemensS7s = new();
|
||||
|
||||
private List<Plc> plcs = new();
|
||||
private List<SiemensS7Net> siemensS7Nets = new();
|
||||
|
||||
[GlobalSetup]
|
||||
public async Task Init()
|
||||
@@ -57,13 +54,7 @@ public class S7Benchmark : IDisposable
|
||||
await siemensS7.ReadAsync("M1", 100);
|
||||
siemensS7s.Add(siemensS7);
|
||||
}
|
||||
for (int i = 0; i < ClientCount; i++)
|
||||
{
|
||||
var siemensS7Net = new SiemensS7Net(SiemensPLCS.S1500, "127.0.0.1");
|
||||
await siemensS7Net.ConnectServerAsync();
|
||||
await siemensS7Net.ReadAsync("M0", 100);
|
||||
siemensS7Nets.Add(siemensS7Net);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ClientCount; i++)
|
||||
{
|
||||
var plc = new Plc(CpuType.S71500, "127.0.0.1", 102, 0, 0);
|
||||
@@ -94,34 +85,6 @@ public class S7Benchmark : IDisposable
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task HslCommunication()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
foreach (var siemensS7Net in siemensS7Nets)
|
||||
{
|
||||
for (int i = 0; i < TaskNumberOfItems; i++)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
for (int i = 0; i < NumberOfItems; i++)
|
||||
{
|
||||
var result = await siemensS7Net.ReadAsync("M0", 100);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new Exception(result.Message);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[Benchmark]
|
||||
public async Task ThingsGateway()
|
||||
@@ -151,7 +114,6 @@ public class S7Benchmark : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
plcs.ForEach(a => a.SafeDispose());
|
||||
siemensS7Nets.ForEach(a => a.SafeDispose());
|
||||
siemensS7s.ForEach(a => a.Channel.SafeDispose());
|
||||
siemensS7s.ForEach(a => a.SafeDispose());
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -47,7 +48,7 @@ public class TimeoutBenchmark
|
||||
var _reusableTimeout = _reusableTimeouts.Get();
|
||||
try
|
||||
{
|
||||
await Task.Delay(5, _reusableTimeout.GetTokenSource(10, otherCts.Token).Token).ConfigureAwait(false); // 模拟工作
|
||||
await Task.Delay(5, _reusableTimeout.GetTokenSource(10, otherCts.Token)).ConfigureAwait(false); // 模拟工作
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace BenchmarkConsoleApp
|
||||
namespace ThingsGateway.Foundation
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
@@ -45,18 +45,18 @@ namespace BenchmarkConsoleApp
|
||||
//ManualConfig.Create(DefaultConfig.Instance)
|
||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
//);
|
||||
BenchmarkRunner.Run<BenchmarkAsyncWaitData>(
|
||||
ManualConfig.Create(DefaultConfig.Instance)
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
);
|
||||
// BenchmarkRunner.Run<BenchmarkAsyncWaitData>(
|
||||
//ManualConfig.Create(DefaultConfig.Instance)
|
||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
//);
|
||||
// BenchmarkRunner.Run<SemaphoreBenchmark>(
|
||||
// ManualConfig.Create(DefaultConfig.Instance)
|
||||
// .WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
//);
|
||||
// BenchmarkRunner.Run<ModbusBenchmark>(
|
||||
//ManualConfig.Create(DefaultConfig.Instance)
|
||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
//);
|
||||
BenchmarkRunner.Run<ModbusBenchmark>(
|
||||
ManualConfig.Create(DefaultConfig.Instance)
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
);
|
||||
// BenchmarkRunner.Run<S7Benchmark>(
|
||||
//ManualConfig.Create(DefaultConfig.Instance)
|
||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
|
||||
@@ -42,10 +42,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
|
||||
<PackageReference Include="HslCommunication" Version="12.5.1" />
|
||||
<PackageReference Include="Longbow.Modbus" Version="9.1.1" />
|
||||
<PackageReference Include="NModbus" Version="3.0.81" />
|
||||
<PackageReference Include="NModbus.Serial" Version="3.0.81" />
|
||||
<PackageReference Include="S7netplus" Version="0.20.0" />
|
||||
<!--<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="$(DefaultVersion)" />-->
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -225,97 +225,97 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
||||
return;
|
||||
else
|
||||
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||
{
|
||||
var bytes = new ByteBlock(256,
|
||||
(c) =>
|
||||
{
|
||||
var data = ArrayPool<byte>.Shared.Rent(c);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
var bytes = new ByteBlock(256,
|
||||
(c) =>
|
||||
{
|
||||
data[i] = 0;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
(m) =>
|
||||
{
|
||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
||||
var data = ArrayPool<byte>.Shared.Rent(c);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = 0;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
(m) =>
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(result.Array);
|
||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(result.Array);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
bytes.SetLength(256);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes.WriteByte(0);
|
||||
}
|
||||
bytes.Position = 0;
|
||||
return bytes;
|
||||
});
|
||||
);
|
||||
bytes.SetLength(256);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes.WriteByte(0);
|
||||
}
|
||||
bytes.Position = 0;
|
||||
return bytes;
|
||||
});
|
||||
|
||||
if (ModbusServer03ByteBlocks.ContainsKey(mAddress.Station))
|
||||
return;
|
||||
else
|
||||
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||
{
|
||||
var bytes = new ByteBlock(256,
|
||||
(c) =>
|
||||
{
|
||||
var data = ArrayPool<byte>.Shared.Rent(c);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
var bytes = new ByteBlock(256,
|
||||
(c) =>
|
||||
{
|
||||
data[i] = 0;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
(m) =>
|
||||
{
|
||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
||||
var data = ArrayPool<byte>.Shared.Rent(c);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = 0;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
(m) =>
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(result.Array);
|
||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(result.Array);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
bytes.SetLength(256);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes.WriteByte(0);
|
||||
}
|
||||
bytes.Position = 0;
|
||||
return bytes;
|
||||
});
|
||||
);
|
||||
bytes.SetLength(256);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes.WriteByte(0);
|
||||
}
|
||||
bytes.Position = 0;
|
||||
return bytes;
|
||||
});
|
||||
|
||||
if (ModbusServer04ByteBlocks.ContainsKey(mAddress.Station))
|
||||
return;
|
||||
else
|
||||
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||
{
|
||||
var bytes = new ByteBlock(256,
|
||||
(c) =>
|
||||
{
|
||||
var data = ArrayPool<byte>.Shared.Rent(c);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
var bytes = new ByteBlock(256,
|
||||
(c) =>
|
||||
{
|
||||
data[i] = 0;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
(m) =>
|
||||
{
|
||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
||||
var data = ArrayPool<byte>.Shared.Rent(c);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = 0;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
(m) =>
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(result.Array);
|
||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(result.Array);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
bytes.SetLength(256);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes.WriteByte(0);
|
||||
}
|
||||
bytes.Position = 0;
|
||||
return bytes;
|
||||
});
|
||||
);
|
||||
bytes.SetLength(256);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes.WriteByte(0);
|
||||
}
|
||||
bytes.Position = 0;
|
||||
return bytes;
|
||||
});
|
||||
}
|
||||
|
||||
public override Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config)
|
||||
@@ -558,7 +558,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseRequest(object requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu)
|
||||
private static bool TryParseRequest(IRequestInfo requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu)
|
||||
{
|
||||
modbusRequest = default;
|
||||
sequences = default;
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ using Photino.Blazor;
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
using ThingsGateway.NewLife.Log;
|
||||
|
||||
namespace ThingsGateway.Server;
|
||||
@@ -28,6 +30,23 @@ internal sealed class Program
|
||||
[STAThread]
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
|
||||
{
|
||||
if (e.ExceptionObject is OutOfMemoryException)
|
||||
{
|
||||
try
|
||||
{
|
||||
XTrace.WriteLine($"[OOM DETECTED]");
|
||||
XTrace.WriteLine(MachineInfo.GetCurrent().ToJsonNetString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
//当前工作目录设为程序集的基目录
|
||||
System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
|
||||
// 增加中文编码支持
|
||||
|
||||
@@ -64,12 +64,10 @@
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<!--<PublishAot>true</PublishAot>-->
|
||||
<CETCompat>false</CETCompat>
|
||||
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -85,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" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<Import Project="..\Version.props" />
|
||||
|
||||
@@ -37,17 +37,15 @@
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<!--<PublishAot>true</PublishAot>-->
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
<PublishAot>true</PublishAot>
|
||||
<DebugType>none</DebugType>
|
||||
<EmbedAllSources>false</EmbedAllSources>
|
||||
<EmitDebugInformation>false</EmitDebugInformation>
|
||||
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -32,26 +32,14 @@
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--<TieredCompilation>false</TieredCompilation>-->
|
||||
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe-->
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"CheckInterval": 1800000, //检查间隔
|
||||
"MaxChannelCount": 50, //最大通道数量
|
||||
"MaxDeviceCount": 50, //最大设备数量
|
||||
"MaxVariableCount": 10000 //最大变量数量
|
||||
"MaxVariableCount": 5000 //最大变量数量
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
|
||||
"RemoteServerManagement": {
|
||||
"Enable": true,
|
||||
"Enable": false,
|
||||
"Name": "ThingsGateway",
|
||||
"ServerUri": "0.0.0.0:8399",
|
||||
"VerifyToken": "ThingsGateway",
|
||||
|
||||
18
src/ThingsGateway.Server/Layout/Gitee2025opensource.razor
Normal file
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
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,9 +109,10 @@ public class Startup : AppStartup
|
||||
.AddInteractiveServerComponents(options =>
|
||||
{
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 5;
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
@@ -119,29 +120,31 @@ public class Startup : AppStartup
|
||||
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
|
||||
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
|
||||
options.StreamBufferCapacity = 30;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
|
||||
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
|
||||
});
|
||||
|
||||
#else
|
||||
|
||||
services.AddServerSideBlazor(options =>
|
||||
{
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
}).AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
options.MaximumReceiveMessageSize =32 * 1024 * 1024;
|
||||
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
|
||||
options.StreamBufferCapacity = 30;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
||||
});
|
||||
{
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 5;
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
|
||||
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
|
||||
options.StreamBufferCapacity = 30;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
|
||||
});
|
||||
|
||||
#endif
|
||||
|
||||
@@ -205,10 +208,7 @@ public class Startup : AppStartup
|
||||
|
||||
var websiteOptions = App.GetConfig<WebsiteOptions>("Website");
|
||||
|
||||
if (websiteOptions.BlazorConnectionLimitEnable)
|
||||
{
|
||||
services.AddSingleton<CircuitHandler, ConnectionLimiterCircuitHandler>();
|
||||
}
|
||||
services.AddSingleton<CircuitHandler, ConnectionLimiterCircuitHandler>();
|
||||
if (websiteOptions.Demo)
|
||||
{
|
||||
authenticationBuilder.AddOAuth<GiteeOAuthOptions, AdminOAuthHandler<GiteeOAuthOptions>>("Gitee", "Gitee", options =>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||
|
||||
using System.Runtime;
|
||||
|
||||
using ThingsGateway.Common;
|
||||
|
||||
namespace ThingsGateway.Server;
|
||||
@@ -22,6 +24,12 @@ public class ConnectionLimiterCircuitHandler : CircuitHandler
|
||||
|
||||
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
|
||||
{
|
||||
//主动触发垃圾回收,释放上个链路资源
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect(1, GCCollectionMode.Optimized, blocking: false, compacting: false);
|
||||
|
||||
WebsiteOptions ??= App.GetOptions<WebsiteOptions>();
|
||||
|
||||
if (!WebsiteOptions.BlazorConnectionLimitEnable)
|
||||
|
||||
@@ -51,26 +51,11 @@
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--<TieredCompilation>false</TieredCompilation>-->
|
||||
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe-->
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"configProperties": {
|
||||
"System.Runtime.EnableWriteXorExecute": false
|
||||
"System.Runtime.EnableWriteXorExecute": false,
|
||||
"System.GC.HeapHardLimitPercent": 95, //堆限制百分比
|
||||
"System.GC.HighMemoryPercent": 90, //高内存百分比
|
||||
"System.GC.DynamicAdaptationMode": 1, //动态适应模式
|
||||
"System.GC.ConserveMemory": 5 //节省内存模式,0-9
|
||||
//"System.GC.RegionRange": 549755813888 //8GB, 区域范围,保留的虚拟内存,如DOCKER内出现OOM,可以调大,一般是进程内存限制的2倍
|
||||
}
|
||||
}
|
||||
|
||||
302
wiki.json
Normal file
302
wiki.json
Normal file
@@ -0,0 +1,302 @@
|
||||
{
|
||||
"repo_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"title": "Overview",
|
||||
"purpose": "Introduce ThingsGateway, explaining what it is, its core purpose as an industrial IoT gateway, and the high-level architecture",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Key Features",
|
||||
"purpose": "List and describe the main features including multi-protocol support, plugin architecture, data persistence options, and deployment models",
|
||||
"parent": "Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "System Requirements and Dependencies",
|
||||
"purpose": "Document the .NET versions, target frameworks, required NuGet packages, and system requirements",
|
||||
"parent": "Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Getting Started",
|
||||
"purpose": "Provide quick-start guide for installing and running ThingsGateway",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Installation and Deployment",
|
||||
"purpose": "Explain deployment options including Docker (x64/ARM64), web server, desktop application, and configuration basics",
|
||||
"parent": "Getting Started",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Initial Configuration",
|
||||
"purpose": "Guide users through initial setup including appsettings.json, environment variables, logging configuration, and first channel/device setup",
|
||||
"parent": "Getting Started",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Architecture Overview",
|
||||
"purpose": "Explain the overall system architecture including layered design, separation of concerns, and component relationships",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Layered Architecture",
|
||||
"purpose": "Describe the Foundation, Gateway Application, and UI/Admin layers and their responsibilities",
|
||||
"parent": "Architecture Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin System Architecture",
|
||||
"purpose": "Explain the plugin architecture including Foundation vs Plugin layer separation, dynamic loading with AssemblyLoadContext, and plugin types",
|
||||
"parent": "Architecture Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Build System and Conditional Compilation",
|
||||
"purpose": "Document the MSBuild-based build system including Directory.Build.props, conditional plugin loading, multi-targeting, and NuGet package generation",
|
||||
"parent": "Architecture Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Foundation Layer",
|
||||
"purpose": "Document the core foundation libraries that provide protocol implementations and communication primitives",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Device Communication",
|
||||
"purpose": "Explain DeviceBase abstraction, IDevice interface, protocol implementations, read/write operations, and data parsing",
|
||||
"parent": "Foundation Layer",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Channel Architecture",
|
||||
"purpose": "Document IChannel interface, channel implementations (TCP/UDP/Serial), channel options, and connection lifecycle",
|
||||
"parent": "Foundation Layer",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Data Handling and Transformation",
|
||||
"purpose": "Describe DataHandlingAdapter, message parsing, ThingsGatewayBitConverter for endianness/format conversion, and WaitHandlePool for request-response correlation",
|
||||
"parent": "Foundation Layer",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Core Utilities",
|
||||
"purpose": "Document TimerX scheduling, ExpiringDictionary caching, WaitLock concurrency primitives, and Reflect utilities",
|
||||
"parent": "Foundation Layer",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Gateway Application",
|
||||
"purpose": "Explain the main gateway application layer that orchestrates device communication and data flow",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Runtime Management System",
|
||||
"purpose": "Document the entity-to-runtime conversion, GlobalData static registry, RuntimeServiceHelper, and runtime synchronization",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Device Lifecycle Management",
|
||||
"purpose": "Explain DeviceThreadManage, driver initialization, StartAsync/StopAsync lifecycle, TaskSchedulerLoop, and thread management",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Variable Management",
|
||||
"purpose": "Document VariableRuntime, VariableSourceRead, variable packing optimization, and data collection workflow",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Concurrency and Read/Write Coordination",
|
||||
"purpose": "Explain AsyncReadWriteLock, reader/writer priority, duty cycle control, and concurrent operation management",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Event System",
|
||||
"purpose": "Document GlobalData event dispatchers, VariableValueChangeEvent, DeviceStatusChangeEvent, AlarmChangedEvent, and event flow",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Redundancy and Failover",
|
||||
"purpose": "Explain device redundancy system, master/slave configuration, failover triggers, and variable state transfer",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin Development",
|
||||
"purpose": "Guide for developing custom plugins for data collection and business logic integration",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Collection Plugins (CollectBase)",
|
||||
"purpose": "Document how to create data collection plugins including protocol implementation, variable loading, and scheduled tasks",
|
||||
"parent": "Plugin Development",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Business Plugins (BusinessBase)",
|
||||
"purpose": "Explain business plugin development for data persistence, distribution, and serving protocols",
|
||||
"parent": "Plugin Development",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin Service and Dynamic Loading",
|
||||
"purpose": "Document PluginService, AssemblyLoadContext usage, plugin discovery, driver instantiation, and property configuration",
|
||||
"parent": "Plugin Development",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin Packaging and Distribution",
|
||||
"purpose": "Explain how to package plugins as NuGet packages, versioning, and deployment via Directory.build.targets",
|
||||
"parent": "Plugin Development",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Built-in Protocols",
|
||||
"purpose": "Document the built-in protocol implementations including Modbus, Siemens S7, OPC UA, OPC DA, and Dlt645",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Modbus Protocol",
|
||||
"purpose": "Document Modbus Master/Slave implementations, RTU/TCP/UDP support, function codes, and address parsing",
|
||||
"parent": "Built-in Protocols",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Siemens S7 Protocol",
|
||||
"purpose": "Explain S7 Master implementation, PLC types, area addressing, and data block access",
|
||||
"parent": "Built-in Protocols",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user