Compare commits

..

8 Commits

Author SHA1 Message Date
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
2248356998 qq.com
96b4287f3a fix: operResult隐式转换递归错误 2025-10-22 13:43:39 +08:00
2248356998 qq.com
7d406de29f 调整 runtimeconfig.template.json 文件 2025-10-22 00:47:33 +08:00
53 changed files with 1149 additions and 641 deletions

View File

@@ -1,11 +1,18 @@
# ThingsGateway # ThingsGateway
 [![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 ## Introduction
 
A cross-platform, high-performance edge data collection gateway based on net9. A cross-platform, high-performance edge data collection gateway based on net8/10.
 
## Documentation ## Documentation
@@ -29,7 +36,6 @@ Account: **SuperAdmin**
Password: **111111** Password: **111111**
 
**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**
## Docker ## Docker

View File

@@ -1,8 +1,18 @@
# ThingsGateway # ThingsGateway
[![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 +29,6 @@
密码 : **111111** 密码 : **111111**
**右上角个人弹出框中,切换到物联网关模块**
## Docker ## Docker

View File

@@ -8,6 +8,9 @@
// QQ群605534569 // QQ群605534569
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using System.ComponentModel;
using System.Runtime;
using ThingsGateway.NewLife; using ThingsGateway.NewLife;
namespace ThingsGateway.Admin.Application; namespace ThingsGateway.Admin.Application;
@@ -20,10 +23,6 @@ public class HardwareInfo
/// </summary> /// </summary>
public DriveInfo DriveInfo { get; set; } public DriveInfo DriveInfo { get; set; }
/// <summary>
/// 硬件信息获取
/// </summary>
public MachineInfo? MachineInfo { get; set; }
/// <summary> /// <summary>
/// 主机环境 /// 主机环境
@@ -40,19 +39,118 @@ public class HardwareInfo
/// </summary> /// </summary>
public string OsArchitecture { get; set; } public string OsArchitecture { get; set; }
/// <summary>
/// 唯一编码
/// </summary>
public string UUID { get; set; }
/// <summary> /// <summary>系统名称</summary>
/// 进程占用内存 public String OSName { get; set; }
/// </summary>
[AutoGenerateColumn(Ignore = true)] /// <summary>系统版本</summary>
public int WorkingSet { get; set; } public String OSVersion { get; set; }
public String UUID { get; set; }
/// <summary>内存总量。单位MB</summary>
public UInt64 Memory { get; set; }
/// <summary>可用内存。单位MB</summary>
public UInt64 AvailableMemory { get; set; }
/// <summary>CPU占用率</summary>
public Double CpuRate { get; set; }
public Double Battery { get; set; }
public Double Temperature { get; set; }
/// <summary>处理器型号</summary>
public String? Processor { get; set; }
#region GC与进程内存信息
/// <summary>GC 认为“内存吃紧”的阈值。单位MB</summary>
[DisplayName("GC高内存阈值")]
public UInt64 HighMemoryLoadThreshold { get; set; }
/// <summary>GC 可用内存上限。单位MB</summary>
[DisplayName("GC可用内存上限")]
public UInt64 TotalAvailableMemory { get; set; }
/// <summary>当前托管堆容量。单位MB</summary>
[DisplayName("托管堆容量")]
public UInt64 HeapSize { get; set; }
/// <summary>托管堆已用内存。单位MB</summary>
[DisplayName("托管堆已用")]
public UInt64 TotalMemory { get; set; }
/// <summary>托管堆碎片大小。单位MB</summary>
[DisplayName("托管堆碎片")]
public UInt64 FragmentedBytes { get; set; }
/// <summary>GC识别可用内存。单位MB</summary>
[DisplayName("GC识别可用内存")]
public UInt64 GCAvailableMemory { get; set; }
/// <summary>GC 已提交的内存。单位MB</summary>
[DisplayName("GC已提交内存")]
public UInt64 CommittedBytes { get; set; }
/// <summary>GC 累计分配的托管内存。单位MB</summary>
[DisplayName("GC累计分配")]
public UInt64 TotalAllocatedBytes { get; set; }
/// <summary>GC 暂停累计时间。单位:毫秒</summary>
[DisplayName("GC累计暂停时间")]
public UInt64 TotalPauseDurationMs { get; set; }
/// <summary>GC 代0收集次数</summary>
[DisplayName("GC Gen0 次数")]
public Int32 GcGen0Count { get; set; }
/// <summary>GC 代1收集次数</summary>
[DisplayName("GC Gen1 次数")]
public Int32 GcGen1Count { get; set; }
/// <summary>GC 代2收集次数</summary>
[DisplayName("GC Gen2 次数")]
public Int32 GcGen2Count { get; set; }
/// <summary>Server GC 是否启用</summary>
[DisplayName("是否使用Server GC")]
public Boolean IsServerGC { get; set; }
/// <summary>GC 延迟模式</summary>
[DisplayName("GC延迟模式")]
public GCLatencyMode? GCLatencyMode { get; set; }
/// <summary>GC 固定对象数</summary>
[DisplayName("固定对象数")]
public Int64 PinnedObjectsCount { get; set; }
/// <summary>终结队列挂起对象数</summary>
[DisplayName("终结挂起数")]
public Int64 FinalizationPendingCount { get; set; }
#endregion
#region
/// <summary>进程虚拟内存使用量。单位MB</summary>
[DisplayName("虚拟内存")]
public UInt64 VirtualMemory { get; set; }
/// <summary>进程私有内存使用量。单位MB</summary>
[DisplayName("私有内存")]
public UInt64 PrivateMemory { get; set; }
/// <summary>进程峰值工作集。单位MB</summary>
[DisplayName("峰值工作集")]
public UInt64 PeakWorkingSet { get; set; }
/// <summary>进程当前工作集。单位MB</summary>
[DisplayName("当前工作集")]
public UInt64 WorkingSet { get; set; }
#endregion
/// <summary> /// <summary>
/// 更新时间 /// 更新时间
/// </summary> /// </summary>
public string UpdateTime { get; set; } public DateTime UpdateTime { get; set; }
public ulong AppRunTotalMinute { get; set; }
public ulong SystemRunTotalMinute { get; set; }
} }

View File

@@ -8,12 +8,10 @@
// QQ群605534569 // QQ群605534569
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using ThingsGateway.Extension;
using ThingsGateway.NewLife; using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Caching; using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Threading; using ThingsGateway.NewLife.Threading;
@@ -43,7 +41,7 @@ public class HardwareJob : IJob, IHardwareJob
/// <summary> /// <summary>
/// 运行信息获取 /// 运行信息获取
/// </summary> /// </summary>
public HardwareInfo HardwareInfo { get; } = new(); public HardwareInfo HardwareInfo { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public HardwareInfoOptions HardwareInfoOptions { get; private set; } public HardwareInfoOptions HardwareInfoOptions { get; private set; }
@@ -76,9 +74,10 @@ public class HardwareJob : IJob, IHardwareJob
{ {
try try
{ {
if (HardwareInfo.MachineInfo == null) var machine = MachineInfo.GetCurrent();
if (HardwareInfo == null)
{ {
HardwareInfo.MachineInfo = MachineInfo.GetCurrent(); HardwareInfo=machine.AdaptHardwareInfo();
string currentPath = Directory.GetCurrentDirectory(); string currentPath = Directory.GetCurrentDirectory();
DriveInfo drive = new(Path.GetPathRoot(currentPath)); DriveInfo drive = new(Path.GetPathRoot(currentPath));
@@ -88,10 +87,9 @@ public class HardwareJob : IJob, IHardwareJob
HardwareInfo.DriveInfo = drive; HardwareInfo.DriveInfo = drive;
HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构 HardwareInfo.OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(); // 系统架构
HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架 HardwareInfo.FrameworkDescription = RuntimeInformation.FrameworkDescription; // NET框架
HardwareInfo.Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production"; HardwareInfo.Environment = App.HostEnvironment.EnvironmentName;
HardwareInfo.UUID = HardwareInfo.MachineInfo.UUID;
HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat(); HardwareInfo.UpdateTime = TimerX.Now;
} }
} }
catch catch
@@ -99,9 +97,12 @@ public class HardwareJob : IJob, IHardwareJob
} }
try try
{ {
HardwareInfo.MachineInfo.Refresh(); var machine = MachineInfo.GetCurrent();
HardwareInfo.UpdateTime = TimerX.Now.ToDefaultDateTimeFormat(); machine.Refresh();
HardwareInfo.WorkingSet = (Environment.WorkingSet / 1024.0 / 1024.0).ToInt(); machine.AdaptHardwareInfo(HardwareInfo);
HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 /60;
HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 /60;
HardwareInfo.UpdateTime = TimerX.Now;
error = false; error = false;
} }
catch (Exception ex) catch (Exception ex)
@@ -123,10 +124,10 @@ public class HardwareJob : IJob, IHardwareJob
{ {
Date = TimerX.Now, Date = TimerX.Now,
DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(), DriveUsage = (100 - (HardwareInfo.DriveInfo.TotalFreeSpace * 100.00 / HardwareInfo.DriveInfo.TotalSize)).ToInt(),
Battery = (HardwareInfo.MachineInfo.Battery * 100).ToInt(), Battery = (HardwareInfo.Battery * 100).ToInt(),
MemoryUsage = (HardwareInfo.WorkingSet), MemoryUsage = (HardwareInfo.WorkingSet),
CpuUsage = (HardwareInfo.MachineInfo.CpuRate * 100).ToInt(), CpuUsage = (HardwareInfo.CpuRate * 100).ToInt(),
Temperature = (HardwareInfo.MachineInfo.Temperature).ToInt(), Temperature = (HardwareInfo.Temperature).ToInt(),
}; };
await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false); await _db.InsertableT(his).ExecuteCommandAsync(stoppingToken).ConfigureAwait(false);
MemoryCache.Remove(CacheKey); MemoryCache.Remove(CacheKey);

View File

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

View File

@@ -46,12 +46,44 @@
"FileTypeError": "Not supported format {0}" "FileTypeError": "Not supported format {0}"
}, },
"ThingsGateway.Admin.Application.HardwareInfo": { "ThingsGateway.Admin.Application.HardwareInfo": {
"Environment": "HostEnvironment", "DriveInfo": "Current Disk Info",
"FrameworkDescription": ".NETFramework", "AppRunTotalMinute": "AppRunTotalMinute(min)",
"OsArchitecture": "System Architecture", "SystemRunTotalMinute": "SystemRunTotalMinute(min)",
"UpdateTime": "UpdateTime", "Environment": "Host Environment",
"UUID": "UUID" "FrameworkDescription": ".NET Framework",
"OsArchitecture": "OS Architecture",
"OSName": "OS Name",
"OSVersion": "OS Version",
"UUID": "UUID",
"Memory": "Total Memory",
"AvailableMemory": "Available Memory",
"CpuRate": "CPU Usage",
"Battery": "Battery Level",
"Temperature": "Temperature",
"Processor": "Processor Model",
"HighMemoryLoadThreshold": "GC High Memory Threshold",
"TotalAvailableMemory": "GC Total Available Memory",
"HeapSize": "Managed Heap Size",
"TotalMemory": "Managed Heap Used",
"FragmentedBytes": "Managed Heap Fragmented",
"GCAvailableMemory": "GC Available Memory",
"CommittedBytes": "GC Committed Bytes",
"TotalAllocatedBytes": "GC Total Allocated (MB)",
"TotalPauseDurationMs": "GC Total Pause Duration (ms)",
"GcGen0Count": "GC Gen0 Count",
"GcGen1Count": "GC Gen1 Count",
"GcGen2Count": "GC Gen2 Count",
"IsServerGC": "Server GC",
"GCLatencyMode": "GC Latency Mode",
"PinnedObjectsCount": "Pinned Objects Count",
"FinalizationPendingCount": "Finalization Pending Count",
"VirtualMemory": "Virtual Memory",
"PrivateMemory": "Private Memory",
"PeakWorkingSet": "Peak Working Set",
"WorkingSet": "Current Working Set",
"UpdateTime": "Update Time"
}, },
"ThingsGateway.Admin.Application.HardwareJob": { "ThingsGateway.Admin.Application.HardwareJob": {
"GetHardwareInfoFail": "Get Hardwareinfo Fail" "GetHardwareInfoFail": "Get Hardwareinfo Fail"
}, },

View File

@@ -46,11 +46,42 @@
"FileTypeError": "不支持 {0} 格式" "FileTypeError": "不支持 {0} 格式"
}, },
"ThingsGateway.Admin.Application.HardwareInfo": { "ThingsGateway.Admin.Application.HardwareInfo": {
"DriveInfo": "当前磁盘信息",
"AppRunTotalMinute": "软件运行时长(min)",
"SystemRunTotalMinute": "系统运行时长(min)",
"Environment": "主机环境", "Environment": "主机环境",
"FrameworkDescription": "NET框架", "FrameworkDescription": ".NET 框架",
"OsArchitecture": "系统架构", "OsArchitecture": "系统架构",
"UpdateTime": "更新时间", "OSName": "系统名称",
"UUID": "唯一编码" "OSVersion": "系统版本",
"UUID": "UUID",
"Memory": "系统总内存",
"AvailableMemory": "系统可用内存",
"CpuRate": "CPU 占用率",
"Battery": "电池电量",
"Temperature": "温度",
"Processor": "处理器型号",
"HighMemoryLoadThreshold": "GC 高内存阈值",
"TotalAvailableMemory": "GC 总内存阈值",
"HeapSize": "托管堆容量",
"TotalMemory": "托管对象占用",
"FragmentedBytes": "托管堆碎片",
"GCAvailableMemory": "GC 可用内存",
"CommittedBytes": "GC 提交内存总量",
"TotalAllocatedBytes": "GC 累计分配(MB)",
"TotalPauseDurationMs": "GC 累计暂停时间(ms)",
"GcGen0Count": "GC Gen0 次数",
"GcGen1Count": "GC Gen1 次数",
"GcGen2Count": "GC Gen2 次数",
"IsServerGC": "Server GC",
"GCLatencyMode": "GC 延迟模式",
"PinnedObjectsCount": "固定对象数",
"FinalizationPendingCount": "终结挂起数",
"VirtualMemory": "虚拟内存",
"PrivateMemory": "私有内存",
"PeakWorkingSet": "峰值工作集",
"WorkingSet": "当前工作集",
"UpdateTime": "更新时间"
}, },
"ThingsGateway.Admin.Application.HardwareJob": { "ThingsGateway.Admin.Application.HardwareJob": {
"GetHardwareInfoFail": "获取硬件信息出错" "GetHardwareInfoFail": "获取硬件信息出错"

View File

@@ -8,13 +8,20 @@
// QQ群605534569 // QQ群605534569
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Riok.Mapperly.Abstractions; using Riok.Mapperly.Abstractions;
using ThingsGateway.NewLife;
namespace ThingsGateway.Admin.Application; namespace ThingsGateway.Admin.Application;
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)] [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
public static partial class AdminMapper public static partial class AdminMapper
{ {
public static partial HardwareInfo AdaptHardwareInfo(this MachineInfo src);
public static partial void AdaptHardwareInfo(this MachineInfo src, HardwareInfo dto);
public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src); public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src);
public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src); public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src);
public static partial SessionOutput AdaptSessionOutput(this SysUser src); public static partial SessionOutput AdaptSessionOutput(this SysUser src);

View File

@@ -19,7 +19,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Rougamo.Fody" Version="5.0.2" /> <PackageReference Include="Rougamo.Fody" Version="5.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">

View File

@@ -22,6 +22,9 @@
"SetDefaultModule": "Set as default module" "SetDefaultModule": "Set as default module"
}, },
"ThingsGateway.Admin.Razor.HardwareInfoPage": { "ThingsGateway.Admin.Razor.HardwareInfoPage": {
"GCMemoryInfoConfig": "GCMemoryInfoConfig(MB)",
"GCMemoryInfo": "GCMemoryInfo(MB)",
"GCAnalyzeInfo": "GCAnalyzeInfo",
"AvailableDisk": "Available Disk", "AvailableDisk": "Available Disk",
"AvailableMemory": "Available Memory", "AvailableMemory": "Available Memory",
"CpuUsage": "CPU Usage", "CpuUsage": "CPU Usage",

View File

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

View File

@@ -5,131 +5,189 @@
@using ThingsGateway.Admin.Application @using ThingsGateway.Admin.Application
@namespace ThingsGateway.Admin.Razor @namespace ThingsGateway.Admin.Razor
<div class="row g-2 mx-1 form-inline"> <div class="row g-2 mx-1 form-inline">
<div class="col-12 col-md-12">
<div class="col-12 col-md-4">
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary"> <Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
<HeaderTemplate> <HeaderTemplate>
@Localizer["SystemInfo"] <div class="d-flex justify-content-between align-items-center w-100">
</HeaderTemplate> <span>@Localizer["SystemInfo"]</span>
<small class="text-muted">
<BodyTemplate> @HardwareJob.HardwareInfo.UpdateTime.ToString("yyyy-MM-dd HH:mm:ss")
<EditorFormObject IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow="1" RowType=RowType.Inline LabelWidth="160"> </small>
<FieldItems>
<EditorItem @bind-Field="@context.MachineInfo">
<EditTemplate Context="value">
<div class="col-12 col-md-12">
<Display @bind-Value="@value.MachineInfo.OSName" DisplayText="@Localizer[nameof(value.MachineInfo.OSName)]" ShowTooltip ShowLabel=true>
</Display>
</div>
<div class="col-12 col-md-12">
<Display @bind-Value="@value.MachineInfo.OSVersion" DisplayText="@Localizer[nameof(value.MachineInfo.OSVersion)]" ShowTooltip ShowLabel=true>
</Display>
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.UUID">
<EditTemplate Context="value">
<div class="col-12 col-md-12">
<Display @bind-Value="@value.UUID" ShowTooltip ShowLabel=true>
</Display>
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.DriveInfo" Ignore=true>
</EditorItem>
</FieldItems>
</EditorFormObject>
</BodyTemplate>
</Card>
</div>
<div class="col-12 col-md-8">
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
<HeaderTemplate>
@Localizer["HardwareInfo"]
</HeaderTemplate>
<BodyTemplate>
<div class="d-flex align-items-center justify-content-center w-100 h-100" >
<div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100">
<div class="col-12 col-md-4 justify-content-center h-100" >
@{
var item = HardwareJob.HardwareInfo.MachineInfo.CpuRate;
}
<div class="d-flex flex-column align-items-center">
<Circle Width="200" class="m-3"
Value=@((int)(item*100<=100?item*100:100))
Color=@((item*100>70?Color.Warning:Color.Success))
StrokeWidth="4" ShowProgress=false>
<div class="circle-hardware">
<span>
@Localizer["CpuUsage"] <i> @((item * 100).ToString("F0") + " %")</i>
</span>
</div>
</Circle>
<div class="mt-1">
<span>@(HardwareJob.HardwareInfo.MachineInfo.Processor)</span>
</div>
</div>
</div>
<div class="col-12 col-md-4 justify-content-center h-100">
@{
var availableMemory = HardwareJob.HardwareInfo.MachineInfo.AvailableMemory;
var memory = HardwareJob.HardwareInfo.MachineInfo.Memory;
}
<div class="d-flex flex-column align-items-center ">
<Circle Width="200" class="m-3"
Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100))
Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success))
StrokeWidth="4">
<div class="circle-hardware">
<h6> @((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %") </h6>
<span> @Localizer["WorkingSet"] <i> @(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span>
<span> @Localizer["AvailableMemory"] <i> @((availableMemory / 1024.00 / 1024).ToString("F2") + " GB")</i></span>
<span> @Localizer["TotalMemory"] <i> @((memory / 1024.00 / 1024).ToString("F2") + " GB")</i></span>
</div>
</Circle>
<div class="mt-1">
<span> @Localizer["MemoryUsage"] </span>
</div>
</div>
</div>
<div class="col-12 col-md-4 justify-content-center h-100">
@{
var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace;
var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize;
}
<div class="d-flex flex-column align-items-center ">
<Circle Width="200" class="m-3"
Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100))
Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success))
StrokeWidth="4">
<div class="circle-hardware">
<h6> @((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6>
<span> @(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel) </span>
<span> @Localizer["AvailableDisk"] <i> @((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
<span> @Localizer["TotalDisk"] <i> @((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
</div>
</Circle>
<div class="mt-1">
<span> @Localizer["DiskUsage"] </span>
</div>
</div>
</div>
</div>
</div> </div>
</HeaderTemplate>
<BodyTemplate>
<EditorForm class="mt-3" AutoGenerateAllItem="false" IsDisplay Model="HardwareJob.HardwareInfo" ItemsPerRow=3 RowType=RowType.Inline LabelWidth=300>
<FieldItems>
<EditorItem @bind-Field="@context.OSName" GroupName="@Localizer["SystemInfo"]" />
<EditorItem @bind-Field="@context.OsArchitecture" GroupName="@Localizer["SystemInfo"]" />
<EditorItem @bind-Field="@context.OSVersion" GroupName="@Localizer["SystemInfo"]" />
<EditorItem @bind-Field="@context.Environment" GroupName="@Localizer["SystemInfo"]" />
<EditorItem @bind-Field="@context.FrameworkDescription" GroupName="@Localizer["SystemInfo"]" />
<EditorItem @bind-Field="@context.UUID" GroupName="@Localizer["SystemInfo"]" />
<EditorItem @bind-Field="@context.SystemRunTotalMinute" GroupName="@Localizer["SystemInfo"]" />
<EditorItem @bind-Field="@context.AppRunTotalMinute" GroupName="@Localizer["SystemInfo"]" />
<EditorItem @bind-Field="@context.Memory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
<EditorItem @bind-Field="@context.TotalAvailableMemory" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
<EditorItem @bind-Field="@context.HighMemoryLoadThreshold" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2 />
<EditorItem TValue="bool" TModel="HardwareInfo" @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryConfig"]" GroupOrder=2>
<EditTemplate Context="value">
<div class="col-12">
<h6></h6>
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.IsServerGC" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
<EditorItem @bind-Field="@context.GCLatencyMode" GroupName="@Localizer["GCMemoryInfoConfig"]" GroupOrder=2 />
<EditorItem @bind-Field="@context.WorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem @bind-Field="@context.PrivateMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem @bind-Field="@context.PeakWorkingSet" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3>
<EditTemplate Context="value">
<div class="col-12">
<h6></h6>
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.HeapSize" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem @bind-Field="@context.TotalMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem @bind-Field="@context.FragmentedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem @bind-Field="@context.CommittedBytes" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3>
<EditTemplate Context="value">
<div class="col-12">
<h6></h6>
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.AvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem @bind-Field="@context.GCAvailableMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem @bind-Field="@context.VirtualMemory" GroupName="@Localizer["GCMemoryInfo"]" GroupOrder=3 />
<EditorItem @bind-Field="@context.GcGen0Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
<EditorItem @bind-Field="@context.GcGen1Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
<EditorItem @bind-Field="@context.GcGen2Count" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
<EditorItem TValue="ulong" TModel="HardwareInfo" @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4>
<EditTemplate Context="value">
<div class="col-12">
<h6></h6>
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.TotalAllocatedBytes" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
<EditorItem @bind-Field="@context.TotalPauseDurationMs" GroupName="@Localizer["GCAnalyzeInfo"]" GroupOrder=4 />
</FieldItems>
</EditorForm>
</BodyTemplate> </BodyTemplate>
</Card> </Card>
</div> </div>
</div> </div>
<div class="row g-2 mx-1 form-inline mt-2">
<div class="col-12 col-md-12">
<Card IsShadow=true class="m-2 flex-fill" Color="Color.Primary">
<HeaderTemplate>
@Localizer["HardwareInfo"]
</HeaderTemplate>
<BodyTemplate>
<div class="d-flex align-items-center justify-content-center w-100 h-100">
<div class="row g-2 mx-1 form-inline d-flex justify-content-center w-100">
<!-- ✅ CPU 使用率 -->
<div class="col-12 col-md-4 justify-content-center h-100">
@{
var cpuUsage = HardwareJob.HardwareInfo.CpuRate;
}
<div class="d-flex flex-column align-items-center">
<Circle Width="200" class="m-3"
Value=@((int)(cpuUsage*100<=100?cpuUsage*100:100))
Color=@((cpuUsage*100>70?Color.Warning:Color.Success))
StrokeWidth="4" ShowProgress=false>
<div class="circle-hardware">
<span>
@Localizer["CpuUsage"] <i>@((cpuUsage * 100).ToString("F0") + " %")</i>
</span>
</div>
</Circle>
<div class="mt-1">
<span>@(HardwareJob.HardwareInfo.Processor)</span>
</div>
</div>
</div>
<!-- ✅ 内存使用率 -->
<div class="col-12 col-md-4 justify-content-center h-100">
@{
var availableMemory = HardwareJob.HardwareInfo.AvailableMemory;
var memory = HardwareJob.HardwareInfo.Memory;
}
<div class="d-flex flex-column align-items-center">
<Circle Width="200" class="m-3"
Value=@((int)(memory>availableMemory?100 - (availableMemory * 100.00 / memory):100))
Color=@((availableMemory * 100.00 / memory<20?Color.Warning:Color.Success))
StrokeWidth="4">
<div class="circle-hardware">
<h6>@((100 - (availableMemory * 100.00 / memory)).ToString("F2") + " %")</h6>
<span>@Localizer["WorkingSet"] <i>@(HardwareJob.HardwareInfo.WorkingSet + " MB")</i></span>
<span>@Localizer["AvailableMemory"] <i>@((availableMemory / 1024.00).ToString("F2") + " GB")</i></span>
<span>@Localizer["TotalMemory"] <i>@((memory / 1024.00).ToString("F2") + " GB")</i></span>
</div>
</Circle>
<div class="mt-1">
<span>@Localizer["MemoryUsage"]</span>
</div>
</div>
</div>
<!-- ✅ 磁盘使用率 -->
<div class="col-12 col-md-4 justify-content-center h-100">
@{
var totalFreeSpace = HardwareJob.HardwareInfo.DriveInfo.TotalFreeSpace;
var totalSize = HardwareJob.HardwareInfo.DriveInfo.TotalSize;
}
<div class="d-flex flex-column align-items-center">
<Circle Width="200" class="m-3"
Value=@((int)(totalSize>totalFreeSpace?100 - (totalFreeSpace * 100.00 / totalSize):100))
Color=@((totalFreeSpace * 100.00 / totalSize<20?Color.Warning:Color.Success))
StrokeWidth="4">
<div class="circle-hardware">
<h6>@((100 - (totalFreeSpace * 100.00 / totalSize)).ToString("F2") + " %")</h6>
<span>@(HardwareJob.HardwareInfo.DriveInfo.VolumeLabel)</span>
<span>@Localizer["AvailableDisk"] <i>@((totalFreeSpace / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
<span>@Localizer["TotalDisk"] <i>@((totalSize / 1024.00 / 1024 / 1024).ToString("F2") + " GB")</i></span>
</div>
</Circle>
<div class="mt-1">
<span>@Localizer["DiskUsage"]</span>
</div>
</div>
</div>
</div>
</div>
</BodyTemplate>
</Card>
</div>
</div>
<div class="row g-2 mx-1 form-inline"> <div class="row g-2 mx-1 form-inline">
<div class="col-12 col-md-12"> <div class="col-12 col-md-12">
@@ -140,7 +198,7 @@
</HeaderTemplate> </HeaderTemplate>
<BodyTemplate> <BodyTemplate>
<Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="()=>{chartInit=true;return Task.CompletedTask;}" /> <Chart @ref=LineChart OnInitAsync="OnInit" Height="var(--line-chart-height)" Width="100%" OnAfterInitAsync="() => { chartInit = true; return Task.CompletedTask; }" />
</BodyTemplate> </BodyTemplate>
</Card> </Card>
</div> </div>

View File

@@ -92,7 +92,8 @@ public class Startup : AppStartup
options.RootComponents.MaxJSRootComponents = 500; options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
options.MaxBufferedUnacknowledgedRenderBatches = 20; options.MaxBufferedUnacknowledgedRenderBatches = 20;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
}) })
.AddHubOptions(options => .AddHubOptions(options =>
{ {
@@ -103,6 +104,7 @@ public class Startup : AppStartup
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2); options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
options.KeepAliveInterval = TimeSpan.FromSeconds(15); options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.HandshakeTimeout = TimeSpan.FromSeconds(30); options.HandshakeTimeout = TimeSpan.FromSeconds(30);
}); });
#else #else
@@ -112,7 +114,8 @@ public class Startup : AppStartup
options.RootComponents.MaxJSRootComponents = 500; options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
options.MaxBufferedUnacknowledgedRenderBatches = 20; options.MaxBufferedUnacknowledgedRenderBatches = 20;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
}).AddHubOptions(options => }).AddHubOptions(options =>
{ {
//单个传入集线器消息的最大大小。默认 32 KB //单个传入集线器消息的最大大小。默认 32 KB

View File

@@ -15,14 +15,10 @@
<PublishReadyToRunComposite>true</PublishReadyToRunComposite> <PublishReadyToRunComposite>true</PublishReadyToRunComposite>
<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon> <ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
<CETCompat>false</CETCompat>
<ServerGarbageCollection>true</ServerGarbageCollection>
<!--动态适用GC--> <!--动态适用GC-->
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> <GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
<CETCompat>false</CETCompat>
<!--使用自托管线程池-->
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
<!--使用工作站GC-->
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
<!--<PlatformTarget>x86</PlatformTarget>--> <!--<PlatformTarget>x86</PlatformTarget>-->
</PropertyGroup> </PropertyGroup>

View File

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

View File

@@ -81,50 +81,16 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
/// <returns></returns> /// <returns></returns>
private string? GetStringSafely(string name) => GetStringFromJson(name); private string? GetStringSafely(string name) => GetStringFromJson(name);
private string? GetStringFromService(string name)
{
// get string from inject service
string? ret = null;
if (jsonLocalizationOptions.DisableGetLocalizerFromService == false)
{
var localizer = Utility.GetStringLocalizerFromService(Assembly, typeName);
if (localizer != null && localizer is not JsonStringLocalizer)
{
var l = localizer[name];
if (!l.ResourceNotFound)
{
ret = l.Value;
}
}
}
return ret;
}
private string? GetStringFromResourceManager(string name)
{
string? ret = null;
if (jsonLocalizationOptions.DisableGetLocalizerFromResourceManager == false)
{
ret = GetStringSafely(name, CultureInfo.CurrentUICulture);
}
return ret;
}
private readonly ConcurrentHashSet<string> _missingManifestCache = []; private readonly ConcurrentHashSet<string> _missingManifestCache = [];
private string? GetStringFromJson(string name) private string? GetStringFromJson(string name)
{ {
// get string from json localization file // get string from json localization file
var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(Assembly, typeName));
var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}"; var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
string? ret = null; string? ret = null;
if (!_missingManifestCache.Contain(cacheKey)) if (!_missingManifestCache.Contain(cacheKey))
{ {
var l = localizerStrings.Find(i => i.Name == name); var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(Assembly, typeName);
if (l is { ResourceNotFound: false }) if (localizerStrings?.TryGetValue(name, out ret) != true)
{
ret = l.Value;
}
else
{ {
// 如果没有找到资源信息则尝试从父类中查找 // 如果没有找到资源信息则尝试从父类中查找
ret ??= GetStringFromBaseType(name); ret ??= GetStringFromBaseType(name);
@@ -150,28 +116,13 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
if (baseType != type) if (baseType != type)
{ {
var baseAssembly = baseType.Assembly; var baseAssembly = baseType.Assembly;
var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(baseAssembly, baseType.FullName!)); var localizerStrings = CacheManager.GetAllHasValueStringsByTypeName(baseAssembly, baseType.FullName!);
var l = localizerStrings.Find(i => i.Name == name); _ = localizerStrings?.TryGetValue(name, out ret);
if (l is { ResourceNotFound: false })
{
ret = l.Value;
}
} }
} }
return ret; return ret;
} }
private List<LocalizedString> MegerResolveLocalizers(IEnumerable<LocalizedString>? localizerStrings)
{
var localizers = new List<LocalizedString>(CacheManager.GetTypeStringsFromResolve(typeName));
if (localizerStrings != null)
{
localizers.AddRange(localizerStrings);
}
return localizers;
}
private void HandleMissingResourceItem(string name) private void HandleMissingResourceItem(string name)
{ {
localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name); localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
@@ -183,7 +134,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
_missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}"); _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}");
} }
private List<LocalizedString>? _allLocalizerdStrings; private LocalizedString[]? _allLocalizerdStrings;
/// <summary> /// <summary>
/// 获取当前语言的所有资源信息 /// 获取当前语言的所有资源信息
@@ -198,7 +149,7 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
?? GetAllStringsFromBase() ?? GetAllStringsFromBase()
?? GetAllStringsFromJson(); ?? GetAllStringsFromJson();
_allLocalizerdStrings = MegerResolveLocalizers(items); _allLocalizerdStrings = items.ToArray();
} }
return _allLocalizerdStrings; return _allLocalizerdStrings;

View File

@@ -7,17 +7,22 @@ using System.Runtime.Versioning;
using System.Security; using System.Security;
using ThingsGateway.NewLife.Collections; using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Data;
using ThingsGateway.NewLife.Log; using ThingsGateway.NewLife.Log;
using ThingsGateway.NewLife.Model; using ThingsGateway.NewLife.Model;
using ThingsGateway.NewLife.Reflection; using ThingsGateway.NewLife.Reflection;
using ThingsGateway.NewLife.Serialization; using ThingsGateway.NewLife.Serialization;
using ThingsGateway.NewLife.Windows; using ThingsGateway.NewLife.Windows;
using System.Diagnostics;
using System.Runtime;
#if NETFRAMEWORK #if NETFRAMEWORK
using System.Management; using System.Management;
using Microsoft.VisualBasic.Devices; using Microsoft.VisualBasic.Devices;
#endif #endif
#if NETFRAMEWORK || NET6_0_OR_GREATER #if NETFRAMEWORK || NET6_0_OR_GREATER
using Microsoft.Win32; using Microsoft.Win32;
@@ -42,7 +47,7 @@ public interface IMachineInfo
/// ///
/// 刷新信息成本较高,建议采用单例模式 /// 刷新信息成本较高,建议采用单例模式
/// </remarks> /// </remarks>
public class MachineInfo : IExtend public class MachineInfo
{ {
#region #region
/// <summary>系统名称</summary> /// <summary>系统名称</summary>
@@ -88,11 +93,11 @@ public class MachineInfo : IExtend
[DisplayName("磁盘序列号")] [DisplayName("磁盘序列号")]
public String? DiskID { get; set; } public String? DiskID { get; set; }
/// <summary>内存总量。单位KB</summary> /// <summary>内存总量。单位MB</summary>
[DisplayName("内存总量")] [DisplayName("内存总量")]
public UInt64 Memory { get; set; } public UInt64 Memory { get; set; }
/// <summary>可用内存。单位KB</summary> /// <summary>可用内存。单位MB</summary>
[DisplayName("可用内存")] [DisplayName("可用内存")]
public UInt64 AvailableMemory { get; set; } public UInt64 AvailableMemory { get; set; }
@@ -116,13 +121,98 @@ public class MachineInfo : IExtend
[DisplayName("电池剩余")] [DisplayName("电池剩余")]
public Double Battery { get; set; } public Double Battery { get; set; }
private readonly Dictionary<String, Object?> _items = []; #if NET6_0_OR_GREATER
IDictionary<String, Object?> IExtend.Items => _items; #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 #endregion
#region #region
@@ -569,10 +659,50 @@ public class MachineInfo : IExtend
else if (Runtime.Linux) else if (Runtime.Linux)
RefreshLinux(); RefreshLinux();
// 刷新 GC 与进程内存信息
RefreshMemoryInfo();
RefreshSpeed(); RefreshSpeed();
Provider?.Refresh(this); 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() private void RefreshWindows()
{ {
@@ -580,8 +710,8 @@ public class MachineInfo : IExtend
ms.Init(); ms.Init();
if (GlobalMemoryStatusEx(ref ms)) if (GlobalMemoryStatusEx(ref ms))
{ {
Memory = (ulong)(ms.ullTotalPhys / 1024.0); Memory = ms.ullTotalPhys / 1024 / 1024;
AvailableMemory = (ulong)(ms.ullAvailPhys / 1024.0); AvailableMemory = ms.ullAvailPhys / 1024 / 1024;
} }
GetSystemTimes(out var idleTime, out var kernelTime, out var userTime); GetSystemTimes(out var idleTime, out var kernelTime, out var userTime);
@@ -675,28 +805,87 @@ public class MachineInfo : IExtend
#endif #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() private void RefreshLinux()
{ {
var dic = ReadInfo("/proc/meminfo"); var (totalMemory, usedMemory) = GetCGroupMemoryUsage();
if (dic != null) if (totalMemory > 0 && usedMemory > 0 && totalMemory < ulong.MaxValue / 2)
{ {
if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty()) Memory = totalMemory / 1024 / 1024;
Memory = (UInt64)str.TrimEnd(" kB").ToLong(); AvailableMemory = (totalMemory - usedMemory) / 1024 / 1024;
}
ulong ma = 0; else
if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty()) {
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();
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温度和主板温度 // A2/A4温度获取BuildrootCPU温度和主板温度

View File

@@ -180,6 +180,12 @@ public static class Runtime
#endregion #endregion
#region #region
public static Int64 AppStartTick = TickCount64;
/// <summary>软件启动以来的毫秒数</summary>
public static Int64 AppTickCount64 => TickCount64-AppStartTick;
#if NETCOREAPP3_1_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
/// <summary>系统启动以来的毫秒数</summary> /// <summary>系统启动以来的毫秒数</summary>
public static Int64 TickCount64 => Environment.TickCount64; public static Int64 TickCount64 => Environment.TickCount64;
@@ -196,6 +202,8 @@ public static class Runtime
} }
#endif #endif
/// <summary>获取当前UTC时间。基于全局时间提供者在星尘应用中会屏蔽服务器时间差</summary> /// <summary>获取当前UTC时间。基于全局时间提供者在星尘应用中会屏蔽服务器时间差</summary>
/// <returns></returns> /// <returns></returns>
public static DateTimeOffset UtcNow => TimerScheduler.GlobalTimeProvider.GetUtcNow(); public static DateTimeOffset UtcNow => TimerScheduler.GlobalTimeProvider.GetUtcNow();
@@ -248,7 +256,7 @@ public static class Runtime
return dic; return dic;
} }
#endregion #endregion
#region #region
private static Boolean? _createConfigOnMissing; private static Boolean? _createConfigOnMissing;

View File

@@ -8,8 +8,6 @@
// QQ群605534569 // QQ群605534569
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using ThingsGateway.NewLife.Collections;
namespace ThingsGateway.NewLife; namespace ThingsGateway.NewLife;
/// <summary> /// <summary>

View File

@@ -61,7 +61,7 @@ public class TextFileLog : Logger, IDisposable
MaxBytes = set.LogFileMaxBytes; MaxBytes = set.LogFileMaxBytes;
Backups = set.LogFileBackups; Backups = set.LogFileBackups;
_Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000, nameof(TextFileLog)) { Async = true }; _Timer = new TimerX(DoWriteAndClose, null, 0_000, 60_000, nameof(TextFileLog)) { Async = true };
_WriteTimer = new TimerX(DoWrite, null, 0_000, 1000, nameof(TextFileLog)) { Async = true }; _WriteTimer = new TimerX(DoWrite, null, 0_000, 1000, nameof(TextFileLog)) { Async = true };
} }
@@ -149,31 +149,67 @@ public class TextFileLog : Logger, IDisposable
/// <summary>获取日志文件路径</summary> /// <summary>获取日志文件路径</summary>
/// <returns></returns> /// <returns></returns>
private String? GetLogFile() private string? GetLogFile()
{ {
// 单日志文件 // 单日志文件
if (_isFile) return LogPath.GetBasePath(); if (_isFile) return LogPath.GetBasePath();
Directory.CreateDirectory(LogPath);
// 目录多日志文件 // 目录多日志文件
var logfile = LogPath.CombinePath(String.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)).GetBasePath(); var baseFile = LogPath.CombinePath(
string.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)
).GetBasePath();
// 是否限制文件大小 // 不限制大小
if (MaxBytes == 0) return logfile; if (MaxBytes == 0) return baseFile;
// 找到今天第一个未达到最大上限的文件
var max = MaxBytes * 1024L * 1024L; var max = MaxBytes * 1024L * 1024L;
var ext = Path.GetExtension(logfile); var ext = Path.GetExtension(FileFormat);
var name = logfile.TrimEnd(ext);
string? latestFile = null;
DateTime latestTime = DateTime.MinValue;
foreach (var path in Directory.EnumerateFiles(LogPath, $"*{ext}", SearchOption.TopDirectoryOnly))
{
try
{
var lastWrite = File.GetLastWriteTimeUtc(path);
if (lastWrite > latestTime)
{
latestTime = lastWrite;
latestFile = path;
}
}
catch { }
}
if (latestFile != null)
{
try
{
var len = new FileInfo(latestFile).Length;
if (len < max)
return latestFile;
}
catch { }
}
var fileNameWithoutExt = Path.Combine(
Path.GetDirectoryName(baseFile)!,
Path.GetFileNameWithoutExtension(baseFile)
);
// 依序找下一个可用文件
for (var i = 1; i < 1024; i++) for (var i = 1; i < 1024; i++)
{ {
if (i > 1) logfile = $"{name}_{i}{ext}"; var nextFile = i == 1 ? $"{fileNameWithoutExt}{ext}" : $"{fileNameWithoutExt}_{i}{ext}";
if (!File.Exists(nextFile))
return nextFile;
var fi = logfile.AsFile();
if (!fi.Exists || fi.Length < max) return logfile;
} }
return null; return null;
} }
#endregion #endregion
#region #region
@@ -189,9 +225,9 @@ public class TextFileLog : Logger, IDisposable
{ {
var writer = LogWriter; var writer = LogWriter;
var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours);
var logFile = GetLogFile(); var logFile = GetLogFile();
if (logFile.IsNullOrEmpty()) return; if (logFile.IsNullOrEmpty()) return;
var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours);
if (!_isFile && logFile != CurrentLogFile) if (!_isFile && logFile != CurrentLogFile)
{ {
@@ -243,43 +279,36 @@ public class TextFileLog : Logger, IDisposable
DirectoryInfo? di = new DirectoryInfo(LogPath); DirectoryInfo? di = new DirectoryInfo(LogPath);
if (di.Exists) if (di.Exists)
{ {
// 删除*.del // 删除 *.del
try try
{ {
var dels = di.GetFiles("*.del"); foreach (var item in di.EnumerateFiles("*.del"))
if (dels?.Length > 0)
{ {
foreach (var item in dels) item.Delete();
{
item.Delete();
}
} }
} }
catch { } catch { }
var ext = Path.GetExtension(FileFormat); var ext = Path.GetExtension(FileFormat);
var fis = di.GetFiles("*" + ext); var fis = di.EnumerateFiles($"*{ext}")
if (fis != null && fis.Length > Backups) .OrderByDescending(e => e.LastWriteTimeUtc)
.Skip(Backups);
foreach (var item in fis)
{ {
// 删除最旧的文件 OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length);
var retain = fis.Length - Backups; try
fis = fis.OrderBy(e => e.LastWriteTimeUtc).Take(retain).ToArray(); {
foreach (var item in fis) item.Delete();
}
catch
{ {
OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length);
try try
{ {
item.Delete(); item.MoveTo(item.FullName + ".del");
} }
catch catch
{ {
try
{
item.MoveTo(item.FullName + ".del");
}
catch
{
}
} }
} }
} }

View File

@@ -80,13 +80,21 @@ public static class XTrace
Log.Error("{0}", ex); Log.Error("{0}", ex);
} }
public static void WriteException(Exception ex, string message)
{
if (!InitLog()) return;
WriteVersion();
Log.Error("{0}, {1}", message, ex);
}
#endregion #endregion
#region #region
static XTrace() static XTrace()
{ {
_ = Runtime.AppTickCount64;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
AppDomain.CurrentDomain.ProcessExit += OnProcessExit; AppDomain.CurrentDomain.ProcessExit += OnProcessExit;

View File

@@ -65,8 +65,21 @@ public class TimerScheduler : IDisposable, ILogFeature
public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System; public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System;
#endregion #endregion
#region #region
private TimerScheduler(String name) => Name = name; private TimerScheduler(String name)
{
Name = name;
_processCallback = state =>
{
try
{
Execute(state);
}
catch (Exception ex)
{
XTrace.WriteException(ex, "Timer执行错误");
}
};
}
/// <summary>销毁</summary> /// <summary>销毁</summary>
public void Dispose() public void Dispose()
{ {
@@ -258,17 +271,7 @@ public class TimerScheduler : IDisposable, ILogFeature
else else
//Task.Factory.StartNew(() => ProcessItem(timer)); //Task.Factory.StartNew(() => ProcessItem(timer));
// 不需要上下文流动,捕获所有异常 // 不需要上下文流动,捕获所有异常
ThreadPool.UnsafeQueueUserWorkItem(s => ThreadPool.UnsafeQueueUserWorkItem(_processCallback, timer);
{
try
{
Execute(s);
}
catch (Exception ex)
{
XTrace.WriteException(ex);
}
}, timer);
} }
} }
} }
@@ -283,7 +286,7 @@ public class TimerScheduler : IDisposable, ILogFeature
WriteLog("调度线程已退出:{0}", Name); WriteLog("调度线程已退出:{0}", Name);
} }
private readonly WaitCallback _processCallback;
/// <summary>检查定时器是否到期</summary> /// <summary>检查定时器是否到期</summary>
/// <param name="timer"></param> /// <param name="timer"></param>
/// <param name="now"></param> /// <param name="now"></param>
@@ -325,9 +328,9 @@ public class TimerScheduler : IDisposable, ILogFeature
timer.hasSetNext = false; timer.hasSetNext = false;
string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; //string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
string timerArg = timer.Timers.ToString(); //string timerArg = timer.Timers.ToString();
using var span = timer.Tracer?.NewSpan(tracerName, timerArg); //using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
var sw = ValueStopwatch.StartNew(); var sw = ValueStopwatch.StartNew();
try try
{ {
@@ -351,7 +354,7 @@ public class TimerScheduler : IDisposable, ILogFeature
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
catch (Exception ex) catch (Exception ex)
{ {
span?.SetError(ex, null); //span?.SetError(ex, null);
XTrace.WriteException(ex); XTrace.WriteException(ex);
} }
finally finally
@@ -377,9 +380,9 @@ public class TimerScheduler : IDisposable, ILogFeature
timer.hasSetNext = false; timer.hasSetNext = false;
string tracerName = timer.TracerName ?? "timer:ExecuteAsync"; //string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
string timerArg = timer.Timers.ToString(); //string timerArg = timer.Timers.ToString();
using var span = timer.Tracer?.NewSpan(tracerName, timerArg); //using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
var sw = ValueStopwatch.StartNew(); var sw = ValueStopwatch.StartNew();
try try
{ {
@@ -427,7 +430,7 @@ public class TimerScheduler : IDisposable, ILogFeature
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事 // 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
catch (Exception ex) catch (Exception ex)
{ {
span?.SetError(ex, null); //span?.SetError(ex, null);
XTrace.WriteException(ex); XTrace.WriteException(ex);
} }
finally finally

View File

@@ -694,8 +694,12 @@ namespace ThingsGateway.SqlSugar
var enumerator = table.GetEnumerator(); var enumerator = table.GetEnumerator();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
var cur = enumerator.Current; var kvp = enumerator.Current;
yield return cur.Value.Item2[rowIndex]; 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) foreach (var column in columns)
{ {
if (column.IsIgnore) if (column.IsIgnore)
{ {
continue; continue;
@@ -210,6 +211,12 @@ namespace ThingsGateway.SqlSugar
{ {
name = column.PropertyName; name = column.PropertyName;
} }
if (!results.TryGetValue(name, out var tuple) || tuple.Item2 == null)
{
// 某些列可能不在 DataTable 中(例如数据库多了列)
continue;
}
var value = ValueConverter(column, GetValue(item, column)); var value = ValueConverter(column, GetValue(item, column));
if (column.SqlParameterDbType != null && column.SqlParameterDbType is Type && UtilMethods.HasInterface((Type)column.SqlParameterDbType, typeof(ISugarDataConverter))) if (column.SqlParameterDbType != null && column.SqlParameterDbType is Type && UtilMethods.HasInterface((Type)column.SqlParameterDbType, typeof(ISugarDataConverter)))
{ {

View File

@@ -37,7 +37,7 @@
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" /> <PackageReference Include="System.Formats.Asn1" Version="9.0.10" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,18 +1,18 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<PluginVersion>10.12.2</PluginVersion> <PluginVersion>10.12.11</PluginVersion>
<ProPluginVersion>10.12.2</ProPluginVersion> <ProPluginVersion>10.12.11</ProPluginVersion>
<DefaultVersion>10.12.2</DefaultVersion> <DefaultVersion>10.12.11</DefaultVersion>
<AuthenticationVersion>10.11.6</AuthenticationVersion> <AuthenticationVersion>10.11.7</AuthenticationVersion>
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion> <SourceGeneratorVersion>10.11.7</SourceGeneratorVersion>
<NET8Version>8.0.21</NET8Version> <NET8Version>8.0.21</NET8Version>
<NET10Version>10.0.0-rc.2.25502.107</NET10Version> <NET10Version>10.0.0-rc.2.25502.107</NET10Version>
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages> <SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
<IsTrimmable>false</IsTrimmable> <IsTrimmable>false</IsTrimmable>
<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion> <ManagementProPluginVersion>10.11.87</ManagementProPluginVersion>
<ManagementPluginVersion>10.11.87</ManagementPluginVersion> <ManagementPluginVersion>10.11.87</ManagementPluginVersion>
<TSVersion>4.0.0-beta.140</TSVersion> <TSVersion>4.0.0-rc.1</TSVersion>
</PropertyGroup> </PropertyGroup>

View File

@@ -10,53 +10,53 @@
<div class="w-100" style=@($"height:{HeightString}")> <div class="w-100" style=@($"height:{HeightString}")>
<Card HeaderText=@HeaderText class=@("w-100 h-100")> <Card HeaderText=@HeaderText class=@("w-100 h-100")>
<HeaderTemplate> <HeaderTemplate>
<div class="flex-fill"> <div class="flex-fill">
</div> </div>
@if (LogLevelChanged.HasDelegate) @if (LogLevelChanged.HasDelegate)
{ {
<Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select> <Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select>
} }
<Tooltip class=" col-auto" Title="@RazorLocalizer[Pause?"Play":"Pause"]" Placement="Placement.Bottom"> <Tooltip class=" col-auto" Title=@(Pause? PlayText:PauseText) Placement="Placement.Bottom">
<Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause?"fa fa-play":"fa fa-pause") OnClick="OnPause" /> <Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause ? "fa fa-play" : "fa fa-pause") OnClick="OnPause" />
</Tooltip> </Tooltip>
<Tooltip class=" col-auto" Title="@RazorLocalizer["Export"]" Placement="Placement.Bottom"> <Tooltip class=" col-auto" Title="@ExportText" Placement="Placement.Bottom">
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" /> <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" />
</Tooltip> </Tooltip>
<Tooltip class=" col-auto" Title="@RazorLocalizer["Delete"]" Placement="Placement.Bottom"> <Tooltip class=" col-auto" Title="@DeleteText" Placement="Placement.Bottom">
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" /> <Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" />
</Tooltip> </Tooltip>
</HeaderTemplate> </HeaderTemplate>
<BodyTemplate> <BodyTemplate>
<div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")> <div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")>
<Virtualize Items="CurrentMessages??new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2> <Virtualize Items="CurrentMessages ?? new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2>
<ItemContent> <ItemContent>
@* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@ @* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@
<div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"": <div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"":
itemMessage.Level>=(byte)Microsoft.Extensions.Logging.LogLevel.Warning? " red--text text-truncate":"green--text text-truncate") itemMessage.Level >= (byte)Microsoft.Extensions.Logging.LogLevel.Warning ? " red--text text-truncate" : "green--text text-truncate")
title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))>
@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150)) @itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150))
</div> </div>
@* </Tooltip> *@ @* </Tooltip> *@
</ItemContent> </ItemContent>
</Virtualize> </Virtualize>
</div> </div>
</BodyTemplate> </BodyTemplate>
</Card> </Card>
</div> </div>

View File

@@ -10,12 +10,11 @@
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using System.Diagnostics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ThingsGateway.Extension; using ThingsGateway.Extension;
using ThingsGateway.Foundation; using ThingsGateway.Foundation;
using ThingsGateway.NewLife; using ThingsGateway.NewLife.Threading;
using TouchSocket.Core; using TouchSocket.Core;
@@ -23,6 +22,24 @@ namespace ThingsGateway.Debug;
public partial class LogConsole : IDisposable public partial class LogConsole : IDisposable
{ {
private string PlayText { get; set; }
private string PauseText { get; set; }
private string ExportText { get; set; }
private string DeleteText { get; set; }
protected override void OnInitialized()
{
PlayText = RazorLocalizer["Play"];
PauseText = RazorLocalizer["Pause"];
ExportText = RazorLocalizer["Export"];
DeleteText = RazorLocalizer["Delete"];
_Timer = new TimerX(RunTimerAsync, null, 1_000, 1_000, nameof(LogConsole)) { Async = true };
base.OnInitialized();
}
private TimerX _Timer;
private bool Pause; private bool Pause;
public bool Disposed { get; set; } public bool Disposed { get; set; }
@@ -69,7 +86,7 @@ public partial class LogConsole : IDisposable
{ {
logPath = LogPath; logPath = LogPath;
Messages = new List<LogMessage>(); Messages = new List<LogMessage>();
await ExecuteAsync(); _Timer?.SetNext(0);
} }
await base.OnParametersSetAsync(); await base.OnParametersSetAsync();
@@ -82,63 +99,38 @@ public partial class LogConsole : IDisposable
public void Dispose() public void Dispose()
{ {
Disposed = true; Disposed = true;
_Timer?.SafeDispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private WaitLock WaitLock = new(nameof(LogConsole)); protected async ValueTask ExecuteAsync()
protected async Task ExecuteAsync()
{ {
if (WaitLock.Waited) return;
try
{
await WaitLock.WaitAsync();
await Task.Delay(1000);
if (LogPath != null) if (LogPath != null)
{
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
if (!files.IsSuccess)
{ {
var files = await TextFileReadService.GetLogFilesAsync(LogPath); Messages = new List<LogMessage>();
if (!files.IsSuccess) await Task.Delay(1000);
}
else
{
var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault());
if (result.IsSuccess)
{ {
Messages = new List<LogMessage>(); Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToArray();
await Task.Delay(1000);
} }
else else
{ {
await Task.Run(async () => Messages = Array.Empty<LogMessage>();
{
Stopwatch sw = Stopwatch.StartNew();
var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault());
if (result.IsSuccess)
{
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
}
else
{
Messages = new List<LogMessage>();
}
sw.Stop();
if (sw.ElapsedMilliseconds > 500)
{
await Task.Delay(1000);
}
});
} }
} }
} }
catch (Exception ex)
{
NewLife.Log.XTrace.WriteException(ex);
}
finally
{
WaitLock.Release();
}
} }
protected override void OnInitialized()
{
_ = RunTimerAsync();
base.OnInitialized();
}
private async Task Delete() private async Task Delete()
{ {
@@ -185,19 +177,9 @@ public partial class LogConsole : IDisposable
return Task.CompletedTask; return Task.CompletedTask;
} }
private async Task RunTimerAsync() private async Task RunTimerAsync(object? state)
{ {
while (!Disposed) await ExecuteAsync();
{ await InvokeAsync(StateHasChanged);
try
{
await ExecuteAsync();
await InvokeAsync(StateHasChanged);
}
catch (Exception ex)
{
NewLife.Log.XTrace.WriteException(ex);
}
}
} }
} }

View File

@@ -28,7 +28,7 @@ public static class ChannelOptionsExtensions
/// <param name="e">接收数据</param> /// <param name="e">接收数据</param>
/// <param name="funcs">事件</param> /// <param name="funcs">事件</param>
/// <returns></returns> /// <returns></returns>
internal static ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs) internal static ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
{ {
clientChannel.ThrowIfNull(nameof(IClientChannel)); clientChannel.ThrowIfNull(nameof(IClientChannel));
e.ThrowIfNull(nameof(ReceivedDataEventArgs)); e.ThrowIfNull(nameof(ReceivedDataEventArgs));
@@ -44,8 +44,8 @@ public static class ChannelOptionsExtensions
{ {
var func = funcs[i]; var func = funcs[i];
if (func == null) continue; if (func == null) continue;
var taskResult= func.Invoke(clientChannel, e, i == funcs.Count - 1); var taskResult = func.Invoke(clientChannel, e, i == funcs.Count - 1);
if(!taskResult.IsCompletedSuccessfully) if (!taskResult.IsCompletedSuccessfully)
{ {
await taskResult.ConfigureAwait(false); await taskResult.ConfigureAwait(false);
} }

View File

@@ -97,13 +97,15 @@ public static class PluginUtil
{ {
action += a => action += a =>
{ {
a.UseTcpSessionCheckClear() a.UseTcpSessionCheckClear(options =>
.SetCheckClearType(CheckClearType.All) {
.SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime)) options.CheckClearType = CheckClearType.All;
.SetOnClose((c, t) => options.Tick = TimeSpan.FromMilliseconds(channelOptions.CheckClearTime);
{ options.OnClose = (c, t) =>
return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout"); {
}); return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout");
};
});
}; };
} }
return action; return action;

View File

@@ -139,8 +139,15 @@ public struct OperResult<T> : IOperResult<T>
/// <param name="operResult"></param> /// <param name="operResult"></param>
public static implicit operator OperResult(OperResult<T> operResult) public static implicit operator OperResult(OperResult<T> operResult)
{ {
return new OperResult(operResult); return new OperResult
{
OperCode = operResult.OperCode,
ErrorMessage = operResult.ErrorMessage,
Exception = operResult.Exception,
ErrorType = operResult.ErrorType
};
} }
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.ComponentModel; using System.ComponentModel;
using ThingsGateway.NewLife.DictionaryExtensions;
using ThingsGateway.FriendlyException; using ThingsGateway.FriendlyException;

View File

@@ -327,9 +327,12 @@ public static class GlobalData
{ {
if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null) if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null)
return true; return true;
else if (GlobalData.IdDevices.Any(a => a.Value.RedundantDeviceId == deviceRuntime.Id))
var id = deviceRuntime.Id;
foreach (var kv in GlobalData.IdDevices)
{ {
return true; if (kv.Value.RedundantDeviceId == id)
return true;
} }
} }
return false; return false;

View File

@@ -386,7 +386,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
ManageHelper.CheckChannelCount(insertData.Count); ManageHelper.CheckChannelCount(insertData.Count);
using var db = GetDB(); 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.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
await db.BulkUpdateAsync(upData, 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); ManageHelper.CheckDeviceCount(insertData.Count);
using var db = GetDB(); 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.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false); await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);

View File

@@ -113,6 +113,28 @@ public partial class ManagementTask : AsyncDisposableObject
.ConfigurePlugins(a => .ConfigurePlugins(a =>
{ {
a.UseTcpSessionCheckClear(); 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 => a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b =>
{ {
b.UseSystemTextJson(json => b.UseSystemTextJson(json =>
@@ -131,10 +153,6 @@ public partial class ManagementTask : AsyncDisposableObject
a.Add<FilePlugin>(); a.Add<FilePlugin>();
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
.SetMaxFailCount(3);
a.AddDmtpCreatedChannelPlugin(async () => a.AddDmtpCreatedChannelPlugin(async () =>
{ {
try try
@@ -194,9 +212,6 @@ public partial class ManagementTask : AsyncDisposableObject
a.Add<FilePlugin>(); a.Add<FilePlugin>();
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
.SetMaxFailCount(3);
}); });
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false); await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);

View File

@@ -63,7 +63,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
const int highMemorySize = 100000; const int highMemorySize = 100000;
const long memoryThreshold = 2L * 1024 * 1024; // 2GB单位KB 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 ? highMemorySize
: defaultSize; : defaultSize;
} }
@@ -345,7 +345,25 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
.ConfigurePlugins(a => .ConfigurePlugins(a =>
{ {
a.UseTcpSessionCheckClear(); 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 => a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b =>
{ {
b.UseSystemTextJson(json => b.UseSystemTextJson(json =>
@@ -358,9 +376,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
json.Converters.Add(new JArraySystemTextJsonConverter()); 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()); json.Converters.Add(new JArraySystemTextJsonConverter());
}); });
})); }));
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
.SetMaxFailCount(redundancy.MaxErrorCount);
}); });
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false); await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);

View File

@@ -207,7 +207,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
var result = await db.UseTranAsync(async () => 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(newChannels, 10000).ConfigureAwait(false);
await db.BulkCopyAsync(newDevices, 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 () => 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(newChannels, 10000).ConfigureAwait(false);
await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false); await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false);
@@ -424,7 +424,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
if (differences?.Count > 0) if (differences?.Count > 0)
{ {
var data = models.ToList(); var data = models.ToList();
await GlobalData.CheckByDeviceIds(data.Select(a => a.DeviceId)).ConfigureAwait(false); await GlobalData.CheckByDeviceIds(data.Select(a => a.DeviceId)).ConfigureAwait(false);
using var db = GetDB(); using var db = GetDB();
var result = (await db.Updateable(data).UpdateColumns(differences.Select(a => a.Key).ToArray()).ExecuteCommandAsync().ConfigureAwait(false)) > 0; var result = (await db.Updateable(data).UpdateColumns(differences.Select(a => a.Key).ToArray()).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
@@ -493,10 +493,10 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(GatewayExportFilter exportFilter) private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(GatewayExportFilter exportFilter)
{ {
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
List<long>? filterDeviceIds= null; List<long>? filterDeviceIds = null;
if(dataScope!=null) if (dataScope != null)
{ {
filterDeviceIds= GlobalData.GetCurrentUserDeviceIds(dataScope).ToList(); filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
} }
HashSet<long>? deviceId = null; HashSet<long>? deviceId = null;
if (!exportFilter.PluginName.IsNullOrWhiteSpace()) if (!exportFilter.PluginName.IsNullOrWhiteSpace())
@@ -513,7 +513,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId) .WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId)) .WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
.WhereIF(filterDeviceIds != null , u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询 .WhereIF(filterDeviceIds != null, u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString())); .WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()));
return whereQuery; return whereQuery;
@@ -620,7 +620,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
[OperDesc("ExportVariable", isRecordPar: false, localizerType: typeof(Variable))] [OperDesc("ExportVariable", isRecordPar: false, localizerType: typeof(Variable))]
public async Task<Dictionary<string, object>> ExportVariableAsync(GatewayExportFilter exportFilter) 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); var whereQuery = await GetWhereEnumerableFunc(exportFilter).ConfigureAwait(false);
//导出 //导出
@@ -694,7 +694,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
{ {
ManageHelper.CheckVariableCount(insertData.Count); ManageHelper.CheckVariableCount(insertData.Count);
using var db = GetDB(); 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.BulkCopyAsync(insertData, 10000).ConfigureAwait(false);
await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false); await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);

View File

@@ -8,7 +8,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" /> <PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Rougamo.Fody" Version="5.0.2" /> <PackageReference Include="Rougamo.Fody" Version="5.0.2" />
<PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" /> <PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" />
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />--> <!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />-->

View File

@@ -1347,27 +1347,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
private TreeViewItem<ChannelDeviceTreeItem> UnknownTreeViewItem; private TreeViewItem<ChannelDeviceTreeItem> UnknownTreeViewItem;
SmartTriggerScheduler? scheduler; SmartTriggerScheduler? scheduler;
private bool _initialized;
public override async Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
if (!_initialized)
{
_initialized = true;
OnInitialized();
await OnInitializedAsync();
OnParametersSet();
StateHasChanged();
await OnParametersSetAsync();
}
else
{
OnParametersSet();
StateHasChanged();
await OnParametersSetAsync();
}
}
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {

View File

@@ -10,7 +10,9 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams\ThingsGateway.Blazor.Diagrams.csproj" /> <ProjectReference Include="..\ThingsGateway.Blazor.Diagrams\ThingsGateway.Blazor.Diagrams.csproj" />
<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" /> <ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" />
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" /> <ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" />
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" /> <ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -8,14 +8,12 @@
<ApplicationIcon>favicon.ico</ApplicationIcon> <ApplicationIcon>favicon.ico</ApplicationIcon>
<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks> <TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
<CETCompat>false</CETCompat>
<ServerGarbageCollection>true</ServerGarbageCollection>
<!--动态适用GC--> <!--动态适用GC-->
<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode> <GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
<!--使用自托管线程池-->
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
<!--使用工作站GC-->
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -225,97 +225,97 @@ public class ModbusSlave : DeviceBase, IModbusAddress
return; return;
else else
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a => ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a =>
{
var bytes = new ByteBlock(256,
(c) =>
{ {
var data = ArrayPool<byte>.Shared.Rent(c); var bytes = new ByteBlock(256,
for (int i = 0; i < data.Length; i++) (c) =>
{ {
data[i] = 0; var data = ArrayPool<byte>.Shared.Rent(c);
} for (int i = 0; i < data.Length; i++)
return data; {
}, data[i] = 0;
(m) => }
{ return data;
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) },
(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);
bytes.SetLength(256); for (int i = 0; i < bytes.Length; i++)
for (int i = 0; i < bytes.Length; i++) {
{ bytes.WriteByte(0);
bytes.WriteByte(0); }
} bytes.Position = 0;
bytes.Position = 0; return bytes;
return bytes; });
});
if (ModbusServer03ByteBlocks.ContainsKey(mAddress.Station)) if (ModbusServer03ByteBlocks.ContainsKey(mAddress.Station))
return; return;
else else
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a => ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a =>
{
var bytes = new ByteBlock(256,
(c) =>
{ {
var data = ArrayPool<byte>.Shared.Rent(c); var bytes = new ByteBlock(256,
for (int i = 0; i < data.Length; i++) (c) =>
{ {
data[i] = 0; var data = ArrayPool<byte>.Shared.Rent(c);
} for (int i = 0; i < data.Length; i++)
return data; {
}, data[i] = 0;
(m) => }
{ return data;
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) },
(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);
bytes.SetLength(256); for (int i = 0; i < bytes.Length; i++)
for (int i = 0; i < bytes.Length; i++) {
{ bytes.WriteByte(0);
bytes.WriteByte(0); }
} bytes.Position = 0;
bytes.Position = 0; return bytes;
return bytes; });
});
if (ModbusServer04ByteBlocks.ContainsKey(mAddress.Station)) if (ModbusServer04ByteBlocks.ContainsKey(mAddress.Station))
return; return;
else else
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a => ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a =>
{
var bytes = new ByteBlock(256,
(c) =>
{ {
var data = ArrayPool<byte>.Shared.Rent(c); var bytes = new ByteBlock(256,
for (int i = 0; i < data.Length; i++) (c) =>
{ {
data[i] = 0; var data = ArrayPool<byte>.Shared.Rent(c);
} for (int i = 0; i < data.Length; i++)
return data; {
}, data[i] = 0;
(m) => }
{ return data;
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result)) },
(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);
bytes.SetLength(256); for (int i = 0; i < bytes.Length; i++)
for (int i = 0; i < bytes.Length; i++) {
{ bytes.WriteByte(0);
bytes.WriteByte(0); }
} bytes.Position = 0;
bytes.Position = 0; return bytes;
return bytes; });
});
} }
public override Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config) public override Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config)

View File

@@ -15,7 +15,9 @@
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj"> <ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
</ProjectReference> </ProjectReference>
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>

View File

@@ -17,6 +17,8 @@ using Photino.Blazor;
using System.Text; using System.Text;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.NewLife.Log; using ThingsGateway.NewLife.Log;
namespace ThingsGateway.Server; namespace ThingsGateway.Server;
@@ -28,6 +30,23 @@ internal sealed class Program
[STAThread] [STAThread]
private static void Main(string[] args) 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); System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
// 增加中文编码支持 // 增加中文编码支持

View File

@@ -64,12 +64,10 @@
<ApplicationIcon>favicon.ico</ApplicationIcon> <ApplicationIcon>favicon.ico</ApplicationIcon>
<!--<PublishAot>true</PublishAot>--> <!--<PublishAot>true</PublishAot>-->
<CETCompat>false</CETCompat> <CETCompat>false</CETCompat>
<ServerGarbageCollection>true</ServerGarbageCollection>
<!--动态适用GC--> <!--动态适用GC-->
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> <GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
<!--使用工作站GC-->
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
<!--<PlatformTarget>x86</PlatformTarget>--> <!--<PlatformTarget>x86</PlatformTarget>-->
</PropertyGroup> </PropertyGroup>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Razor"> <Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="..\Version.props" /> <Import Project="..\Version.props" />
@@ -37,17 +37,15 @@
<ApplicationIcon>favicon.ico</ApplicationIcon> <ApplicationIcon>favicon.ico</ApplicationIcon>
<!--<PublishAot>true</PublishAot>--> <!--<PublishAot>true</PublishAot>-->
<CETCompat>false</CETCompat> <CETCompat>false</CETCompat>
<ServerGarbageCollection>true</ServerGarbageCollection>
<!--动态适用GC-->
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<EmbedAllSources>false</EmbedAllSources> <EmbedAllSources>false</EmbedAllSources>
<EmitDebugInformation>false</EmitDebugInformation> <EmitDebugInformation>false</EmitDebugInformation>
<!--动态适用GC-->
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
<!--使用工作站GC-->
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
<!--<PlatformTarget>x86</PlatformTarget>--> <!--<PlatformTarget>x86</PlatformTarget>-->
</PropertyGroup> </PropertyGroup>

View File

@@ -32,26 +32,14 @@
<PublishReadyToRunComposite>true</PublishReadyToRunComposite> <PublishReadyToRunComposite>true</PublishReadyToRunComposite>
<ApplicationIcon>favicon.ico</ApplicationIcon> <ApplicationIcon>favicon.ico</ApplicationIcon>
<CETCompat>false</CETCompat>
<ServerGarbageCollection>true</ServerGarbageCollection>
<!--动态适用GC--> <!--动态适用GC-->
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> <GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
<CETCompat>false</CETCompat>
<!--<TieredCompilation>false</TieredCompilation>-->
<!--使用自托管线程池-->
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
<!--使用工作站GC-->
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
<!--<PlatformTarget>x86</PlatformTarget>-->
<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe-->
</PropertyGroup> </PropertyGroup>

View File

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

View File

@@ -16,6 +16,7 @@ using System.Text;
using ThingsGateway.Admin.Application; using ThingsGateway.Admin.Application;
using ThingsGateway.DB; using ThingsGateway.DB;
using ThingsGateway.NewLife; using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.NewLife.Log; using ThingsGateway.NewLife.Log;
using ThingsGateway.SqlSugar; using ThingsGateway.SqlSugar;
@@ -27,6 +28,24 @@ public class Program
public static async Task Main(string[] args) 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); await Task.Delay(2000).ConfigureAwait(false);
//当前工作目录设为程序集的基目录 //当前工作目录设为程序集的基目录
System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory); System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);

View File

@@ -111,7 +111,8 @@ public class Startup : AppStartup
options.RootComponents.MaxJSRootComponents = 500; options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
options.MaxBufferedUnacknowledgedRenderBatches = 20; options.MaxBufferedUnacknowledgedRenderBatches = 20;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
}) })
.AddHubOptions(options => .AddHubOptions(options =>
{ {
@@ -131,7 +132,8 @@ public class Startup : AppStartup
options.RootComponents.MaxJSRootComponents = 500; options.RootComponents.MaxJSRootComponents = 500;
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2); options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
options.MaxBufferedUnacknowledgedRenderBatches = 20; options.MaxBufferedUnacknowledgedRenderBatches = 20;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); options.DisconnectedCircuitMaxRetained = 1;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
}).AddHubOptions(options => }).AddHubOptions(options =>
{ {
//单个传入集线器消息的最大大小。默认 32 KB //单个传入集线器消息的最大大小。默认 32 KB

View File

@@ -51,26 +51,11 @@
<PublishReadyToRunComposite>true</PublishReadyToRunComposite> <PublishReadyToRunComposite>true</PublishReadyToRunComposite>
<ApplicationIcon>favicon.ico</ApplicationIcon> <ApplicationIcon>favicon.ico</ApplicationIcon>
<CETCompat>false</CETCompat>
<ServerGarbageCollection>true</ServerGarbageCollection>
<!--动态适用GC--> <!--动态适用GC-->
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode> <GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
<CETCompat>false</CETCompat>
<!--<TieredCompilation>false</TieredCompilation>-->
<!--使用自托管线程池-->
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
<!--使用工作站GC-->
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
<!--<PlatformTarget>x86</PlatformTarget>-->
<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe-->
</PropertyGroup> </PropertyGroup>

View File

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