Compare commits

..

21 Commits

Author SHA1 Message Date
2248356998 qq.com
694437c7d5 更新readme 2025-10-30 18:06:54 +08:00
2248356998 qq.com
0e78cdefe7 feat: 支持net10 sdk下正确编译 net462 2025-10-30 17:57:17 +08:00
2248356998 qq.com
087dc9aaa3 更新logo 2025-10-30 17:20:12 +08:00
2248356998 qq.com
dacf255f1a fix: mqttCollect插件无法订阅两个以上的主题 2025-10-28 23:34:47 +08:00
2248356998 qq.com
e3960ce115 feat(DeviceBase): 发送前检查在线状态 2025-10-28 20:00:24 +08:00
2248356998 qq.com
0cf098be85 feat: bacnet添加debug日志 2025-10-28 19:36:30 +08:00
2248356998 qq.com
c93c80468c build: 10.12.16
fix: 系统总内存在特定情况下单位错误
feat: 网关定时任务性能优化
2025-10-27 17:12:13 +08:00
2248356998 qq.com
a464594885 feat: 网关定时任务性能优化 2025-10-27 14:37:36 +08:00
2248356998 qq.com
b42f8afa35 feat: 通道多设备日志分类显示 2025-10-24 17:46:24 +08:00
2248356998 qq.com
facf8bd401 修改ConnectionLimiterCircuitHandler.cs 2025-10-24 08:27:26 +08:00
2248356998 qq.com
feeb17eca3 启用ConnectionLimiterCircuitHandler 2025-10-24 01:31:15 +08:00
2248356998 qq.com
b3405cd674 修改CircuitHandler.cs 2025-10-24 01:16:35 +08:00
2248356998 qq.com
c35f9cef93 修改CircuitHandler.cs 2025-10-24 01:07:27 +08:00
2248356998 qq.com
3f382202db runtimeconfig.template.json 2025-10-24 01:00:53 +08:00
2248356998 qq.com
2a3493cc82 调整server option项 2025-10-24 00:51:56 +08:00
2248356998 qq.com
aaa459ebe0 feat: 更新socket库 2025-10-23 23:55:11 +08:00
2248356998 qq.com
51a8acbc3e feat: 多语言资源缓存 2025-10-23 23:24:11 +08:00
2248356998 qq.com
dc132a1999 fix: orm实体与表字段不一致时可能导致批量操作失败 2025-10-23 19:21:13 +08:00
Diego
0c31cfcbc5 fix: orm实体与表字段不一致时可能导致批量操作失败 2025-10-23 18:56:34 +08:00
2248356998 qq.com
0e44bc67cd feat: 系统总/可用内存读取 适配docker容器,硬件信息页面增加GC数据显示 2025-10-22 23:24:28 +08:00
2248356998 qq.com
12dfbba42c build: 10.12.6 2025-10-22 15:30:56 +08:00
90 changed files with 1398 additions and 763 deletions

View File

@@ -1,35 +1,44 @@
# ThingsGateway

<p align="center">
<img src="logo.svg" width = "400" height = "200" alt="The name of the image" align=center />
</p>
[![star](https://gitee.com/ThingsGateway/ThingsGateway/badge/star.svg?theme=gvp)](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)
[![star](https://img.shields.io/github/stars/ThingsGateway/ThingsGateway?logo=github)](https://github.com/ThingsGateway/ThingsGateway)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ThingsGateway/ThingsGateway)
[![NuGet(ThingsGateway)](https://img.shields.io/nuget/v/ThingsGateway.Foundation.svg?label=ThingsGateway)](https://www.nuget.org/packages/ThingsGateway.Foundation/)
[![NuGet(ThingsGateway)](https://img.shields.io/nuget/dt/ThingsGateway.Foundation.svg)](https://www.nuget.org/packages/ThingsGateway.Foundation/)
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://thingsgateway.cn/docs/1)
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569">
<img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ">
</a>
## Introduction

A cross-platform, high-performance edge data collection gateway based on net9.

A cross-platform, high-performance edge data collection gateway based on net8/10.
## Documentation

[Documentation](https://thingsgateway.cn/).

[NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)

## Demo

[Demo](https://demo.thingsgateway.cn/)

Account: **SuperAdmin**

Password: **111111**

**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**
## Docker
@@ -44,7 +53,7 @@ docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
### Plugin List

#### Data Collection Plugins
@@ -74,28 +83,28 @@ docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
| TDengineDB | Time-series database storage |
| QuestDB | Time-series database storage |

## License

[License](https://thingsgateway.cn/docs/1)


## Sponsorship

[Sponsorship Approach](https://thingsgateway.cn/docs/1000)

## Community

QQ Group: 605534569 [Jump](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569)

## Pro Plugins

[Plugin List](https://thingsgateway.cn/docs/1001)

View File

@@ -1,8 +1,21 @@
# ThingsGateway
<p align="center">
<img src="logo.svg" width = "400" height = "200" alt="The name of the image" align=center />
</p>
[![star](https://gitee.com/ThingsGateway/ThingsGateway/badge/star.svg?theme=gvp)](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)
[![star](https://img.shields.io/github/stars/ThingsGateway/ThingsGateway?logo=github)](https://github.com/ThingsGateway/ThingsGateway)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ThingsGateway/ThingsGateway)
[![NuGet(ThingsGateway)](https://img.shields.io/nuget/v/ThingsGateway.Foundation.svg?label=ThingsGateway)](https://www.nuget.org/packages/ThingsGateway.Foundation/)
[![NuGet(ThingsGateway)](https://img.shields.io/nuget/dt/ThingsGateway.Foundation.svg)](https://www.nuget.org/packages/ThingsGateway.Foundation/)
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://thingsgateway.cn/docs/1)
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569">
<img src="https://img.shields.io/badge/QQ群-605534569-red" alt="QQ">
</a>
## 介绍
基于net9的跨平台高性能边缘采集网关
基于net8/10的跨平台高性能边缘采集网关
## 文档
@@ -19,7 +32,6 @@
密码 : **111111**
**右上角个人弹出框中,切换到物联网关模块**
## Docker

BIN
icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB

9
logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 236 KiB

View File

@@ -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; }
}

View File

@@ -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);

View File

@@ -21,7 +21,7 @@ public class HistoryHardwareInfo
/// <inheritdoc/>
[SugarColumn(ColumnDescription = "内存")]
public int MemoryUsage { get; set; }
public UInt64 MemoryUsage { get; set; }
/// <inheritdoc/>
[SugarColumn(ColumnDescription = "CPU使用率")]

View File

@@ -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"
},

View File

@@ -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": "获取硬件信息出错"

View File

@@ -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);

View File

@@ -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",

View File

@@ -22,6 +22,10 @@
"SetDefaultModule": "设置为默认模块"
},
"ThingsGateway.Admin.Razor.HardwareInfoPage": {
"GCMemoryInfoConfig": "GC配置信息(MB)",
"GCMemoryInfo": "GC内存信息(MB)",
"GCAnalyzeInfo": "GC统计",
"AvailableDisk": "可用磁盘",
"AvailableMemory": "可用内存",
"CpuUsage": "CPU使用率",

View File

@@ -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>

View File

@@ -87,45 +87,45 @@ public class Startup : AppStartup
#if NET8_0_OR_GREATER
services
.AddRazorComponents(options => options.TemporaryRedirectionUrlValidityDuration = TimeSpan.FromMinutes(10))
.AddInteractiveServerComponents(options =>
{
options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
options.MaxBufferedUnacknowledgedRenderBatches = 20;
options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
})
.AddHubOptions(options =>
{
//单个传入集线器消息的最大大小。默认 32 KB
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
options.StreamBufferCapacity = 30;
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
.AddInteractiveServerComponents(options =>
{
options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
options.MaxBufferedUnacknowledgedRenderBatches = 5;
options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
})
.AddHubOptions(options =>
{
//单个传入集线器消息的最大大小。默认 32 KB
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
options.StreamBufferCapacity = 30;
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
});
#else
services.AddServerSideBlazor(options =>
{
options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
options.MaxBufferedUnacknowledgedRenderBatches = 20;
options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
}).AddHubOptions(options =>
{
//单个传入集线器消息的最大大小。默认 32 KB
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
options.StreamBufferCapacity = 30;
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
{
options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
options.MaxBufferedUnacknowledgedRenderBatches = 5;
options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
})
.AddHubOptions(options =>
{
//单个传入集线器消息的最大大小。默认 32 KB
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
options.StreamBufferCapacity = 30;
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
});
#endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -0,0 +1,134 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Common;
/// <inheritdoc/>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class EnumerableQueryPageOptionsExtensions
{
public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option, out int totalCount, FilterKeyValueAction where = null)
{
totalCount = 0;
if (datas == null)
return new List<T>();
where ??= option.ToFilter();
if (where.HasFilters())
{
datas = datas.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortList.Count > 0)
{
datas = datas.Sort(option.SortList);//name asc模式
}
if (option.AdvancedSortList.Count > 0)
{
datas = datas.Sort(option.AdvancedSortList);//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace())
{
datas = datas.Sort(option.SortName, option.SortOrder);
}
totalCount = datas.Count();
if (option.IsPage)
{
datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems);
}
else if (option.IsVirtualScroll)
{
datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems);
}
return datas;
}
public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
{
if (queryFunc != null)
query = queryFunc(query);
where ??= option.ToFilter();
if (where.HasFilters())
{
query = query.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !string.IsNullOrEmpty(option.SortName))
{
var invoker = Utility.GetSortFunc<T>();
query = invoker(query, option.SortName, option.SortOrder);
}
else if (option.SortList.Count > 0)
{
var invoker = Utility.GetSortListFunc<T>();
query = invoker(query, option.SortList);
}
else if (option.AdvancedSortList.Count > 0)
{
var invoker = Utility.GetSortListFunc<T>();
query = invoker(query, option.AdvancedSortList);
}
return query;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option, FilterKeyValueAction where = null)
{
var ret = new QueryData<T>()
{
IsSorted = option.SortOrder != SortOrder.Unset,
IsFiltered = option.Filters.Count > 0,
IsAdvanceSearch = option.AdvanceSearches.Count > 0 || option.CustomerSearches.Count > 0,
IsSearch = option.Searches.Count > 0
};
var items = datas.GetData(option, out var totalCount, where);
ret.TotalCount = totalCount;
if (totalCount > 0)
{
if (!items.Any() && option.PageIndex != 1)
{
option.PageIndex = 1;
items = datas.GetData(option, out totalCount, where);
}
}
ret.Items = items.ToList();
return ret;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var items = datas.Skip((option.StartIndex)).Take(option.Count);
ret.TotalCount = datas.Count();
ret.Items = func(items).ToList();
return ret;
}
}

View File

@@ -19,7 +19,7 @@ using System.Reflection;
using ThingsGateway.Common.Extension;
namespace ThingsGateway.DB;
namespace ThingsGateway.Common;
/// <summary>
/// 导出excel扩展

View File

@@ -11,7 +11,7 @@
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Http;
namespace ThingsGateway.DB;
namespace ThingsGateway.Common;
/// <inheritdoc/>
[ThingsGateway.DependencyInjection.SuppressSniffer]

View File

@@ -14,6 +14,7 @@ using System.ComponentModel.DataAnnotations;
using ThingsGateway.Common.Extension;
#if NET8_0_OR_GREATER
using System.Collections.Frozen;
#endif
@@ -104,37 +105,6 @@ internal class CacheManager
}
}
/// <summary>
/// 设置 App 开始时间
/// </summary>
public void SetStartTime() => SetStartTime(DateTimeOffset.Now);
/// <summary>
/// 设置 App 开始时间
/// </summary>
private void SetStartTime(DateTimeOffset startDateTimeOffset)
{
GetOrCreate("BootstrapBlazor_StartTime", entry =>
{
entry.Priority = CacheItemPriority.NeverRemove;
return startDateTimeOffset;
});
}
/// <summary>
/// 获取 App 开始时间
/// </summary>
/// <returns></returns>
public DateTimeOffset GetStartTime()
{
var ret = DateTimeOffset.MinValue;
if (Cache.TryGetValue("BootstrapBlazor_StartTime", out var v) && v is DateTimeOffset d)
{
ret = d;
}
return ret;
}
/// <summary>
/// 获得 缓存数量
/// </summary>
@@ -286,8 +256,15 @@ internal class CacheManager
/// </summary>
/// <param name="assembly">Assembly 程序集实例</param>
/// <param name="typeName">类型名称</param>
public static IEnumerable<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName)
public static FrozenSet<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName)
=> GetJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name);
/// <summary>
/// 获取指定文化本地化资源集合
/// </summary>
/// <param name="assembly">Assembly 程序集实例</param>
/// <param name="typeName">类型名称</param>
public static FrozenDictionary<string, string>? GetAllHasValueStringsByTypeName(Assembly assembly, string typeName)
=> GetHasValueJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name);
/// <summary>
/// 通过指定程序集获取所有本地化信息键值集合
@@ -298,7 +275,7 @@ internal class CacheManager
/// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
/// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
/// <returns></returns>
public static IEnumerable<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
public static FrozenSet<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
{
if (assembly.IsDynamic)
{
@@ -308,13 +285,15 @@ internal class CacheManager
cultureName ??= CultureInfo.CurrentUICulture.Name;
if (string.IsNullOrEmpty(cultureName))
{
return [];
return null;
}
var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
var typeKey = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}";
if (forceLoad)
{
Instance.Cache.Remove(key);
Instance.Cache.Remove(typeKey);
}
var localizedItems = Instance.GetOrCreate(key, entry =>
@@ -335,16 +314,77 @@ internal class CacheManager
return items.ToHashSet();
#endif
});
return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase));
}
var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry =>
{
return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase)).ToFrozenSet();
});
return typeLocalizedItems;
}
/// <summary>
/// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
/// 通过指定程序集获取所有本地化信息键值集合
/// </summary>
/// <param name="typeName"></param>
/// <param name="includeParentCultures"></param>
/// <param name="option">JsonLocalizationOptions 实例</param>
/// <param name="assembly">Assembly 程序集实例</param>
/// <param name="typeName">类型名称</param>
/// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
/// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
/// <returns></returns>
public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
public static FrozenDictionary<string, string>? GetHasValueJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
{
if (assembly.IsDynamic)
{
return null;
}
cultureName ??= CultureInfo.CurrentUICulture.Name;
if (string.IsNullOrEmpty(cultureName))
{
return null;
}
var key = $"{CacheKeyPrefix}-{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
var typeKey = $"{CacheKeyPrefix}-{nameof(GetHasValueJsonStringByTypeName)}-{assembly.GetUniqueName()}-{typeName}-{cultureName}";
if (forceLoad)
{
Instance.Cache.Remove(key);
Instance.Cache.Remove(typeKey);
}
var localizedItems = Instance.GetOrCreate(key, entry =>
{
var sections = option.GetJsonStringFromAssembly(assembly, cultureName);
var items = sections.SelectMany(section => section.GetChildren().Select(kv =>
{
var value = kv.Value;
if (value == null && option.UseKeyWhenValueIsNull == true)
{
value = kv.Key;
}
return new LocalizedString(kv.Key, value ?? "", false, section.Key);
}));
#if NET8_0_OR_GREATER
return items.ToFrozenSet();
#else
return items.ToHashSet();
#endif
});
var typeLocalizedItems = Instance.GetOrCreate(typeKey, entry =>
{
return localizedItems.Where(item => item.SearchedLocation!.Equals(typeName, StringComparison.OrdinalIgnoreCase) && !item.ResourceNotFound).ToFrozenDictionary(a => a.Name, a => a.Value);
});
return typeLocalizedItems;
}
///// <summary>
///// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
///// </summary>
///// <param name="typeName"></param>
///// <param name="includeParentCultures"></param>
///// <returns></returns>
//public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
#endregion
#region DisplayName

View File

@@ -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;

View File

@@ -10,7 +10,7 @@
using Yitter.IdGenerator;
namespace ThingsGateway.DB;
namespace ThingsGateway.Common;
/// <summary>
/// 公共功能

View File

@@ -16,74 +16,6 @@ namespace ThingsGateway.DB;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class QueryPageOptionsExtensions
{
public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option, out int totalCount, FilterKeyValueAction where = null)
{
totalCount = 0;
if (datas == null)
return new List<T>();
where ??= option.ToFilter();
if (where.HasFilters())
{
datas = datas.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortList.Count > 0)
{
datas = datas.Sort(option.SortList);//name asc模式
}
if (option.AdvancedSortList.Count > 0)
{
datas = datas.Sort(option.AdvancedSortList);//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace())
{
datas = datas.Sort(option.SortName, option.SortOrder);
}
totalCount = datas.Count();
if (option.IsPage)
{
datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems);
}
else if (option.IsVirtualScroll)
{
datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems);
}
return datas;
}
public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
{
if (queryFunc != null)
query = queryFunc(query);
where ??= option.ToFilter();
if (where.HasFilters())
{
query = query.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !string.IsNullOrEmpty(option.SortName))
{
var invoker = Utility.GetSortFunc<T>();
query = invoker(query, option.SortName, option.SortOrder);
}
else if (option.SortList.Count > 0)
{
var invoker = Utility.GetSortListFunc<T>();
query = invoker(query, option.SortList);
}
else if (option.AdvancedSortList.Count > 0)
{
var invoker = Utility.GetSortListFunc<T>();
query = invoker(query, option.AdvancedSortList);
}
return query;
}
/// <summary>
/// 根据查询条件返回sqlsugar ISugarQueryable
/// </summary>
@@ -111,50 +43,4 @@ public static class QueryPageOptionsExtensions
return query;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option, FilterKeyValueAction where = null)
{
var ret = new QueryData<T>()
{
IsSorted = option.SortOrder != SortOrder.Unset,
IsFiltered = option.Filters.Count > 0,
IsAdvanceSearch = option.AdvanceSearches.Count > 0 || option.CustomerSearches.Count > 0,
IsSearch = option.Searches.Count > 0
};
var items = datas.GetData(option, out var totalCount, where);
ret.TotalCount = totalCount;
if (totalCount > 0)
{
if (!items.Any() && option.PageIndex != 1)
{
option.PageIndex = 1;
items = datas.GetData(option, out totalCount, where);
}
}
ret.Items = items.ToList();
return ret;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var items = datas.Skip((option.StartIndex)).Take(option.Count);
ret.TotalCount = datas.Count();
ret.Items = func(items).ToList();
return ret;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -8,7 +8,7 @@ namespace ThingsGateway.NewLife.Collections;
/// 文档 https://newlifex.com/core/object_pool
/// </remarks>
/// <typeparam name="T"></typeparam>
public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
public class ObjectPoolLock<T> : DisposeBase where T : class
{
#region
/// <summary>名称</summary>
@@ -22,11 +22,6 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
/// <summary>繁忙个数</summary>
public Int32 BusyCount => _BusyCount;
/// <summary>最大个数。默认00表示无上限</summary>
public Int32 Max { get; set; } = 0;
private readonly object _syncRoot = new();
/// <summary>基础空闲集合。只保存最小个数,最热部分</summary>
private readonly Stack<T> _free = new();
@@ -73,7 +68,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
if (_inited) return;
_inited = true;
WriteLog($"Init {typeof(T).FullName} Max={Max}");
WriteLog($"Init {typeof(T).FullName}");
}
}
#endregion
@@ -86,7 +81,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
T? pi = null;
do
{
lock (_syncRoot)
lock (lockThis)
{
if (_free.Count > 0)
{
@@ -95,13 +90,6 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
}
else
{
if (Max > 0 && BusyCount >= Max)
{
var msg = $"申请失败,已有 {BusyCount:n0} 达到或超过最大值 {Max:n0}";
WriteLog("Acquire Max " + msg);
throw new Exception(Name + " " + msg);
}
pi = OnCreate();
if (BusyCount == 0) Init();
@@ -114,7 +102,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
// 如果拿到的对象不可用,则重新借
} while (pi == null || !OnGet(pi));
lock (_syncRoot)
lock (lockThis)
{
// 加入繁忙集合
_busy.Add(pi);
@@ -129,16 +117,12 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
/// <returns></returns>
protected virtual Boolean OnGet(T value) => true;
/// <summary>申请资源包装项Dispose时自动归还到池中</summary>
/// <returns></returns>
public PoolItem<T> GetItem() => new(this, Get());
/// <summary>归还</summary>
/// <param name="value"></param>
public virtual Boolean Return(T value)
{
if (value == null) return false;
lock (_syncRoot)
lock (lockThis)
{
// 从繁忙队列找到并移除缓存项
if (!_busy.Remove(value))
@@ -163,7 +147,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
{
return false;
}
lock (_syncRoot)
lock (lockThis)
{
_free.Push(value);
_FreeCount++;
@@ -180,18 +164,9 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
/// <summary>清空已有对象</summary>
public virtual Int32 Clear()
{
var count = _FreeCount + _BusyCount;
//_busy.Clear();
//_BusyCount = 0;
//_free.Clear();
//while (_free2.TryDequeue(out var rs)) ;
//_FreeCount = 0;
lock (_syncRoot)
lock (lockThis)
{
count = _FreeCount + _BusyCount;
var count = _FreeCount + _BusyCount;
while (_free.Count > 0)
{
@@ -207,9 +182,9 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
}
_busy.Clear();
_BusyCount = 0;
return count;
}
return count;
}
/// <summary>销毁</summary>

View File

@@ -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温度获取BuildrootCPU温度和主板温度

View File

@@ -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();

View File

@@ -153,7 +153,7 @@ public class TextFileLog : Logger, IDisposable
{
// 单日志文件
if (_isFile) return LogPath.GetBasePath();
Directory.CreateDirectory(LogPath);
// 目录多日志文件
var baseFile = LogPath.CombinePath(
string.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)

View File

@@ -94,6 +94,7 @@ public static class XTrace
static XTrace()
{
_ = Runtime.AppTickCount64;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;

View File

@@ -4,7 +4,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net47;netstandard2.0;net6.0;net6.0-windows;net8.0;$(OtherTargetFrameworks);net8.0-windows;</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0;net6.0;net6.0-windows;net8.0;$(OtherTargetFrameworks);net8.0-windows;</TargetFrameworks>
<AssemblyName>ThingsGateway.NewLife.X</AssemblyName>
<RootNamespace>ThingsGateway.NewLife</RootNamespace>
<AssemblyTitle>工具核心库</AssemblyTitle>
@@ -12,9 +12,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
@@ -24,7 +21,7 @@
<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net47' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
<PropertyGroup Condition="'$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
<DefineConstants>__WIN__</DefineConstants>
</PropertyGroup>
@@ -35,11 +32,12 @@
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Memory" Version="4.6.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net47'">
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.Memory" Version="4.6.3" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net47'">
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<Using Include="System.Net.Http" />
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="System.Management" />
@@ -54,10 +52,10 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
<PropertyGroup Condition="$(TargetFramework)=='net6.0'">
<PropertyGroup Condition="$(TargetFramework)=='net6.0' OR $(TargetFramework)=='net6.0-windows'">
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework)=='net8.0'">
<PropertyGroup Condition="$(TargetFramework)=='net8.0' OR $(TargetFramework)=='net8.0-windows'">
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
</PropertyGroup>
@@ -66,7 +64,7 @@
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework)=='net47'">
<PropertyGroup Condition="$(TargetFramework)=='net462'">
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
</PropertyGroup>
@@ -88,6 +86,6 @@
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
</ItemGroup>-->
</Project>

View File

@@ -7,7 +7,6 @@
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Photino.NET" Version="4.0.16" />
</ItemGroup>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -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 };
}
}

View File

@@ -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)))
{

View File

@@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.2" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.905" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.1030" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET10Version)" />-->
<PackageReference Include="MySqlConnector" Version="2.4.0" />

View File

@@ -1,18 +1,18 @@
<Project>
<PropertyGroup>
<PluginVersion>10.12.4</PluginVersion>
<ProPluginVersion>10.12.4</ProPluginVersion>
<DefaultVersion>10.12.4</DefaultVersion>
<AuthenticationVersion>10.11.6</AuthenticationVersion>
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
<PluginVersion>10.12.24</PluginVersion>
<ProPluginVersion>10.12.24</ProPluginVersion>
<DefaultVersion>10.12.24</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>
@@ -22,7 +22,9 @@
<OtherTargetFrameworks>net10.0</OtherTargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<RestoreEnablePackagePruning Condition="'$(TargetFramework)' == 'net462' "> false</RestoreEnablePackagePruning>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' != 'net8.0' ">
<PluginTargetFramework>net10.0</PluginTargetFramework>

View File

@@ -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>
/// 设置数据处理适配器

View File

@@ -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>

View File

@@ -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;

View File

@@ -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/>

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -333,6 +333,10 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
/// <inheritdoc/>
private Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default)
{
if(!channel.Online)
{
throw new InvalidOperationException("Channel is offline");
}
return SendAsync(this, sendMessage, channel, token);
static async PooledTask SendAsync(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken token)
@@ -1060,10 +1064,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
Channel.Collects.Remove(this);
if (Channel is IClientChannel clientChannel)
{
clientChannel.LogSeted(false);
}
}
}
@@ -1118,10 +1118,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
Channel.Collects.Remove(this);
if (Channel is IClientChannel clientChannel)
{
clientChannel.LogSeted(false);
}
}

View File

@@ -21,7 +21,7 @@
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Admin\ThingsGateway.NewLife.X\ThingsGateway.NewLife.X.csproj" />

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -430,7 +430,7 @@ public abstract partial class CollectBase : DriverBase
readErrorCount++;
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
@this.LogMessage?.Trace(string.Format("{0} - Failed to collect data [{1} - {2}] - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
@@ -450,7 +450,7 @@ public abstract partial class CollectBase : DriverBase
{
// 读取成功时记录日志并增加成功计数器
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
@this.LogMessage?.Trace(string.Format("{0} - Collected [{1} - {2}] data successfully {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
}
else
@@ -475,7 +475,7 @@ public abstract partial class CollectBase : DriverBase
if (!cancellationToken.IsCancellationRequested)
{
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
@this.LogMessage?.Trace(string.Format("{0} - Failed to collect data [{1} - {2}] - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
}
}
@@ -566,10 +566,24 @@ public abstract partial class CollectBase : DriverBase
{
foreach (var item in varRead)
{
if (!item.Value.Equals(writeInfoLists[item].ToObject(item.Value?.GetType())))
var cValue = writeInfoLists[item].ToObject(item.RawValue?.GetType());
if (!item.RawValue.Equals(cValue))
{
// 如果写入值与读取值不同,则更新操作结果为失败
operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value, Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
if (cValue is IComparable)
{
operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value, Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
}
else
{
if (cValue != null)
{
if (item.RawValue.ToSystemTextJsonString(false) != cValue.ToSystemTextJsonString(false))
operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value, Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
}
else
operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value, Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
}
}
}
}

View File

@@ -509,34 +509,16 @@ internal sealed class AlarmTask : IDisposable
{
scheduledTask.Change(100, 100);
}
ParallelOptions.CancellationToken = cancellation;
// 遍历设备变量列表
if (!GlobalData.AlarmEnableIdVariables.IsEmpty)
{
// 使用 Parallel.ForEach 执行指定的操作
Parallel.ForEach(GlobalData.AlarmEnableIdVariables, ParallelOptions, (item, state, index) =>
{
// 如果取消请求已经被触发,则结束任务
if (cancellation.IsCancellationRequested)
return;
// 如果该变量的报警功能未启用,则跳过该变量
if (!item.Value.AlarmEnable)
return;
// 如果该变量离线,则跳过该变量
if (!item.Value.IsOnline)
return;
// 对该变量进行报警分析
AlarmAnalysis(item.Value);
});
Parallel.ForEach(GlobalData.AlarmEnableIdVariables, ParallelOptions, Analysis);
}
else
{
//if (scheduledTask.Period != 5000)
// scheduledTask.Change(0, 5000); // 如果没有启用报警的变量则设置下次执行时间为5秒后
scheduledTask.SetNext(5000); // 如果没有启用报警的变量则设置下次执行时间为5秒后
}
@@ -552,6 +534,21 @@ internal sealed class AlarmTask : IDisposable
}
}
private static void Analysis(KeyValuePair<long, VariableRuntime> item, ParallelLoopState state, long index)
{
// 如果取消请求已经被触发,则结束任务
if (state.ShouldExitCurrentIteration)
return;
// 如果该变量的报警功能未启用,则跳过该变量
if (!item.Value.AlarmEnable)
return;
// 如果该变量离线,则跳过该变量
if (!item.Value.IsOnline)
return;
// 对该变量进行报警分析
AlarmAnalysis(item.Value);
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -207,7 +207,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
var result = await db.UseTranAsync(async () =>
{
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
{
await db.BulkCopyAsync(newChannels, 10000).ConfigureAwait(false);
await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false);
@@ -342,7 +342,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
var result = await db.UseTranAsync(async () =>
{
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
{
await db.BulkCopyAsync(newChannels, 10000).ConfigureAwait(false);
await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false);
@@ -620,7 +620,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
[OperDesc("ExportVariable", isRecordPar: false, localizerType: typeof(Variable))]
public async Task<Dictionary<string, object>> ExportVariableAsync(GatewayExportFilter exportFilter)
{
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 4 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 4 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
{
var whereQuery = await GetWhereEnumerableFunc(exportFilter).ConfigureAwait(false);
//导出
@@ -694,7 +694,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
{
ManageHelper.CheckVariableCount(insertData.Count);
using var db = GetDB();
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
if (GlobalData.HardwareJob.HardwareInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
{
await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -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)
@@ -599,29 +599,25 @@ public class ModbusSlave : DeviceBase, IModbusAddress
return WriteError(modbusRtu, client, sequences, e);
}
ValueByteBlock byteBlock = new(1024);
try
{
WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu);
return SendAndDisposeAsync(this, client, e, modbusRtu, byteBlock);
}
catch
{
byteBlock.SafeDispose();
return WriteError(modbusRtu, client, sequences, e);
}
static async PooledTask SendAndDisposeAsync(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, bool modbusRtu, ValueByteBlock byteBlock)
return Write(this, client, e, modbusRequest, sequences, modbusRtu, data);
static async PooledTask Write(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, ModbusRequest modbusRequest, ReadOnlySequence<byte> sequences, bool modbusRtu, OperResult<ReadOnlyMemory<byte>> data)
{
ValueByteBlock byteBlock = new(1024);
try
{
WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu);
await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
}
catch
{
await @this.WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
}
finally
{
byteBlock.SafeDispose(); //即使 ValueByteBlock 是 struct内部仍持有同一块 Memory<byte> 引用,可以安全释放
byteBlock.SafeDispose();
}
}
}
private Task HandleWriteRequestAsync(

View File

@@ -10,6 +10,7 @@
using Riok.Mapperly.Abstractions;
using ThingsGateway.Common;
using ThingsGateway.DB;
namespace ThingsGateway.Plugin.DB;

View File

@@ -171,10 +171,10 @@ public partial class MqttCollect : CollectBase
mqttClientSubscribeOptionsBuilder = mqttClientSubscribeOptionsBuilder.WithTopicFilter(
f => f.WithTopic(item));
}
var mqttClientSubscribeOptions = mqttClientSubscribeOptionsBuilder.Build();
if (mqttClientSubscribeOptions.TopicFilters.Count > 0)
_mqttSubscribeOptions = mqttClientSubscribeOptions;
}
var mqttClientSubscribeOptions = mqttClientSubscribeOptionsBuilder.Build();
if (mqttClientSubscribeOptions.TopicFilters.Count > 0)
_mqttSubscribeOptions = mqttClientSubscribeOptions;
return Task.FromResult(dataResult);
}

View File

@@ -20,6 +20,8 @@ using System.Diagnostics.CodeAnalysis;
using ThingsGateway.Extension;
using ThingsGateway.Foundation.OpcDa;
using ThingsGateway.Foundation.OpcDa.Rcw;
using ThingsGateway.Common;
#if Plugin

View File

@@ -35,6 +35,8 @@ using ThingsGateway.Razor;
using TouchSocket.Core;
using ThingsGateway.Common;
namespace ThingsGateway.Debug;
/// <summary>
@@ -73,7 +75,7 @@ public partial class OpcUaImportVariable
{
Items = BuildTreeItemList(await PopulateBranchAsync(ObjectIds.ObjectsFolder), RenderTreeItem).ToList();
ShowSkeleton = false;
await InvokeAsync(StateHasChanged);
return InvokeAsync(StateHasChanged);
});
}
await base.OnAfterRenderAsync(firstRender);

View File

@@ -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);
// 增加中文编码支持

View File

@@ -83,6 +83,9 @@
<Content Include="..\ThingsGateway.Server\Layout\MainLayout.razor" Link="Layout\MainLayout.razor" />
<Compile Include="..\ThingsGateway.Server\Layout\MainLayout.razor.cs" Link="Layout\MainLayout.razor.cs" />
<Content Include="..\ThingsGateway.Server\Layout\MainLayout.razor.css" Link="Layout\MainLayout.razor.css" />
<Content Include="..\ThingsGateway.Server\Layout\Gitee2025opensource.razor" Link="Layout\Gitee2025opensource.razor" />
<Compile Include="..\ThingsGateway.Server\Layout\Gitee2025opensource.razor.cs" Link="Layout\Gitee2025opensource.razor.cs" />
<Content Include="..\ThingsGateway.Server\Layout\Gitee2025opensource.razor.css" Link="Layout\Gitee2025opensource.razor.css" />
<Content Include="..\ThingsGateway.Server\Layout\AccessDenied.razor" Link="Layout\AccessDenied.razor" />
<Compile Include="..\ThingsGateway.Server\Layout\AccessDenied.razor.cs" Link="Layout\AccessDenied.razor.cs" />
<Content Include="..\ThingsGateway.Server\Layout\Login.razor" Link="Layout\Login.razor" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -3,6 +3,6 @@
"CheckInterval": 1800000, //检查间隔
"MaxChannelCount": 50, //最大通道数量
"MaxDeviceCount": 50, //最大设备数量
"MaxVariableCount": 10000 //最大变量数量
"MaxVariableCount": 5000 //最大变量数量
}
}

View 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>

View 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();
}
}
}

View File

@@ -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;
}

View File

@@ -112,3 +112,4 @@
</CascadingValue>
</CascadingValue>

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -109,8 +109,8 @@ public class Startup : AppStartup
.AddInteractiveServerComponents(options =>
{
options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
options.MaxBufferedUnacknowledgedRenderBatches = 20;
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
options.MaxBufferedUnacknowledgedRenderBatches = 5;
options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
})
@@ -120,30 +120,31 @@ public class Startup : AppStartup
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
options.StreamBufferCapacity = 30;
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
});
#else
services.AddServerSideBlazor(options =>
{
options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
options.MaxBufferedUnacknowledgedRenderBatches = 20;
options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
}).AddHubOptions(options =>
{
//单个传入集线器消息的最大大小。默认 32 KB
options.MaximumReceiveMessageSize =32 * 1024 * 1024;
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
options.StreamBufferCapacity = 30;
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
{
options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
options.MaxBufferedUnacknowledgedRenderBatches = 5;
options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
})
.AddHubOptions(options =>
{
//单个传入集线器消息的最大大小。默认 32 KB
options.MaximumReceiveMessageSize = 32 * 1024 * 1024;
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
options.StreamBufferCapacity = 30;
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
});
#endif
@@ -207,10 +208,7 @@ public class Startup : AppStartup
var websiteOptions = App.GetConfig<WebsiteOptions>("Website");
if (websiteOptions.BlazorConnectionLimitEnable)
{
services.AddSingleton<CircuitHandler, ConnectionLimiterCircuitHandler>();
}
services.AddSingleton<CircuitHandler, ConnectionLimiterCircuitHandler>();
if (websiteOptions.Demo)
{
authenticationBuilder.AddOAuth<GiteeOAuthOptions, AdminOAuthHandler<GiteeOAuthOptions>>("Gitee", "Gitee", options =>

View File

@@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -3,7 +3,8 @@
"System.Runtime.EnableWriteXorExecute": false,
"System.GC.HeapHardLimitPercent": 95, //堆限制百分比
"System.GC.HighMemoryPercent": 90, //高内存百分比
"System.GC.DynamicAdaptationMode": 1 //动态适应模式
"System.GC.DynamicAdaptationMode": 1, //动态适应模式
"System.GC.ConserveMemory": 5 //节省内存模式,0-9
//"System.GC.RegionRange": 549755813888 //8GB, 区域范围保留的虚拟内存如DOCKER内出现OOM可以调大一般是进程内存限制的2倍
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB