Compare commits

...

12 Commits

Author SHA1 Message Date
2248356998 qq.com
a464594885 feat: 网关定时任务性能优化 2025-10-27 14:37:36 +08:00
2248356998 qq.com
b42f8afa35 feat: 通道多设备日志分类显示 2025-10-24 17:46:24 +08:00
2248356998 qq.com
facf8bd401 修改ConnectionLimiterCircuitHandler.cs 2025-10-24 08:27:26 +08:00
2248356998 qq.com
feeb17eca3 启用ConnectionLimiterCircuitHandler 2025-10-24 01:31:15 +08:00
2248356998 qq.com
b3405cd674 修改CircuitHandler.cs 2025-10-24 01:16:35 +08:00
2248356998 qq.com
c35f9cef93 修改CircuitHandler.cs 2025-10-24 01:07:27 +08:00
2248356998 qq.com
3f382202db runtimeconfig.template.json 2025-10-24 01:00:53 +08:00
2248356998 qq.com
2a3493cc82 调整server option项 2025-10-24 00:51:56 +08:00
2248356998 qq.com
aaa459ebe0 feat: 更新socket库 2025-10-23 23:55:11 +08:00
2248356998 qq.com
51a8acbc3e feat: 多语言资源缓存 2025-10-23 23:24:11 +08:00
2248356998 qq.com
dc132a1999 fix: orm实体与表字段不一致时可能导致批量操作失败 2025-10-23 19:21:13 +08:00
Diego
0c31cfcbc5 fix: orm实体与表字段不一致时可能导致批量操作失败 2025-10-23 18:56:34 +08:00
38 changed files with 434 additions and 293 deletions

View File

@@ -11,8 +11,6 @@
using System.ComponentModel;
using System.Runtime;
using ThingsGateway.NewLife;
namespace ThingsGateway.Admin.Application;
/// <inheritdoc/>
@@ -22,7 +20,7 @@ public class HardwareInfo
/// 当前磁盘信息
/// </summary>
public DriveInfo DriveInfo { get; set; }
/// <summary>
/// 主机环境
@@ -151,6 +149,6 @@ public class HardwareInfo
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
public ulong AppRunTotalMinute { get; set; }
public ulong SystemRunTotalMinute { get; set; }
public ulong AppRunTotalMinute { get; set; }
public ulong SystemRunTotalMinute { get; set; }
}

View File

@@ -74,10 +74,10 @@ public class HardwareJob : IJob, IHardwareJob
{
try
{
var machine = MachineInfo.GetCurrent();
var machine = MachineInfo.GetCurrent();
if (HardwareInfo == null)
{
HardwareInfo=machine.AdaptHardwareInfo();
HardwareInfo = machine.AdaptHardwareInfo();
string currentPath = Directory.GetCurrentDirectory();
DriveInfo drive = new(Path.GetPathRoot(currentPath));
@@ -100,8 +100,8 @@ public class HardwareJob : IJob, IHardwareJob
var machine = MachineInfo.GetCurrent();
machine.Refresh();
machine.AdaptHardwareInfo(HardwareInfo);
HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 /60;
HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 /60;
HardwareInfo.AppRunTotalMinute = (ulong)Runtime.AppTickCount64 / 1000 / 60;
HardwareInfo.SystemRunTotalMinute = (ulong)Runtime.TickCount64 / 1000 / 60;
HardwareInfo.UpdateTime = TimerX.Now;
error = false;
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Riok.Mapperly.Abstractions;
using ThingsGateway.NewLife;
@@ -20,7 +18,7 @@ namespace ThingsGateway.Admin.Application;
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 void AdaptHardwareInfo(this MachineInfo src, HardwareInfo dto);
public static partial LoginInput AdaptLoginInput(this OpenApiLoginInput src);
public static partial OpenApiLoginOutput AdaptOpenApiLoginOutput(this LoginOutput src);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,22 +1,20 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Data;
using ThingsGateway.NewLife.Log;
using ThingsGateway.NewLife.Model;
using ThingsGateway.NewLife.Reflection;
using ThingsGateway.NewLife.Serialization;
using ThingsGateway.NewLife.Windows;
using System.Diagnostics;
using System.Runtime;
#if NETFRAMEWORK
using System.Management;
@@ -682,7 +680,7 @@ public class MachineInfo
HeapSize = (ulong)(info.HeapSizeBytes / 1024 / 1024);
TotalMemory = (ulong)(GC.GetTotalMemory(false) / 1024 / 1024);
FragmentedBytes = (ulong)(info.FragmentedBytes / 1024 / 1024);
GCAvailableMemory = (ulong)(info.TotalAvailableMemoryBytes - info.MemoryLoadBytes) / 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

View File

@@ -184,7 +184,7 @@ public static class Runtime
public static Int64 AppStartTick = TickCount64;
/// <summary>软件启动以来的毫秒数</summary>
public static Int64 AppTickCount64 => TickCount64-AppStartTick;
public static Int64 AppTickCount64 => TickCount64 - AppStartTick;
#if NETCOREAPP3_1_OR_GREATER
/// <summary>系统启动以来的毫秒数</summary>
@@ -256,7 +256,7 @@ public static class Runtime
return dic;
}
#endregion
#endregion
#region
private static Boolean? _createConfigOnMissing;

View File

@@ -694,8 +694,12 @@ namespace ThingsGateway.SqlSugar
var enumerator = table.GetEnumerator();
while (enumerator.MoveNext())
{
var cur = enumerator.Current;
yield return cur.Value.Item2[rowIndex];
var kvp = enumerator.Current;
var list = kvp.Value.Item2;
if (list != null && rowIndex < list.Count)
yield return list[rowIndex];
else
yield return new DataInfos { ColumnName = kvp.Key, Value = DBNull.Value };
}
}

View File

@@ -201,6 +201,7 @@ namespace ThingsGateway.SqlSugar
{
foreach (var column in columns)
{
if (column.IsIgnore)
{
continue;
@@ -210,6 +211,12 @@ namespace ThingsGateway.SqlSugar
{
name = column.PropertyName;
}
if (!results.TryGetValue(name, out var tuple) || tuple.Item2 == null)
{
// 某些列可能不在 DataTable 中(例如数据库多了列)
continue;
}
var value = ValueConverter(column, GetValue(item, column));
if (column.SqlParameterDbType != null && column.SqlParameterDbType is Type && UtilMethods.HasInterface((Type)column.SqlParameterDbType, typeof(ISugarDataConverter)))
{

View File

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

View File

@@ -30,7 +30,6 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
WaitHandlePool<MessageBase> WaitHandlePool { get; }
WaitLock GetLock(string key);
void LogSeted(bool logSeted);
/// <summary>
/// 设置数据处理适配器

View File

@@ -80,13 +80,11 @@ public class OtherChannel : SetupConfigObject, IClientChannel
private bool logSet;
/// <inheritdoc/>
public void SetDataHandlingAdapterLogger(ILog log)
{
if (!logSet && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
logSet = true;
handleAdapter.Logger = log;
}
}
@@ -96,12 +94,8 @@ public class OtherChannel : SetupConfigObject, IClientChannel
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <summary>
/// 设置数据处理适配器。
/// </summary>

View File

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

View File

@@ -52,13 +52,11 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <inheritdoc/>
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
private bool logSet;
/// <inheritdoc/>
public void SetDataHandlingAdapterLogger(ILog log)
{
if (!logSet && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
logSet = true;
handleAdapter.Logger = log;
}
}
@@ -68,13 +66,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>

View File

@@ -34,12 +34,10 @@ public class TcpClientChannel : TcpClient, IClientChannel
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
}
private bool logSet;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
logSet = true;
handleAdapter.Logger = log;
}
}
@@ -49,12 +47,8 @@ public class TcpClientChannel : TcpClient, IClientChannel
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();

View File

@@ -25,13 +25,11 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
public TcpSessionClientChannel()
{
}
private bool logSet;
/// <inheritdoc/>
public void SetDataHandlingAdapterLogger(ILog log)
{
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
logSet = true;
handleAdapter.Logger = log;
}
}
@@ -41,12 +39,8 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;

View File

@@ -30,27 +30,21 @@ public class UdpSessionChannel : UdpSession, IClientChannel
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
private bool logSet;
/// <inheritdoc/>
public void SetDataHandlingAdapterLogger(ILog log)
{
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
logSet = true;
handleAdapter.Logger = log;
}
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
SetAdapter(udpDataHandlingAdapter);
logSet = false;
}

View File

@@ -1060,10 +1060,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
Channel.Collects.Remove(this);
if (Channel is IClientChannel clientChannel)
{
clientChannel.LogSeted(false);
}
}
}
@@ -1118,10 +1114,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
Channel.Collects.Remove(this);
if (Channel is IClientChannel clientChannel)
{
clientChannel.LogSeted(false);
}
}

View File

@@ -59,14 +59,15 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
}
return false;
}
private static volatile int NextId = 0;
public void Start()
{
_timer?.Dispose();
if (Check()) return;
if (_taskAction != null)
_timer = new TimerX(TimerCallback, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
_timer = new TimerX(TimerCallback, _state, _interval, $"{nameof(CronScheduledTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false };
else if (_taskFunc != null || _valueTaskFunc != null)
_timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
_timer = new TimerX(TimerCallbackAsync, _state, _interval, $"{nameof(CronScheduledTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false };
}
private ValueTask TimerCallbackAsync(object? state)

View File

@@ -46,11 +46,13 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
}
return false;
}
private static volatile int NextId = 0;
public void Start()
{
_timer?.Dispose();
if (!Check())
_timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { Async = true, Reentrant = false };
_timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, $"{nameof(ScheduledAsyncTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false };
}
private ValueTask DoAsync(object? state)

View File

@@ -36,11 +36,12 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter
}
return false;
}
private static volatile int NextId = 0;
public void Start()
{
_timer?.Dispose();
if (!Check())
_timer = new TimerX(TimerCallback, _state, IntervalMS, IntervalMS, nameof(ScheduledSyncTask)) { Async = true, Reentrant = false };
_timer = new TimerX(TimerCallback, _state, IntervalMS, IntervalMS, $"{nameof(ScheduledSyncTask)}{(Interlocked.Increment(ref NextId) / 100)}") { Async = true, Reentrant = false };
}
private void TimerCallback(object? state)

View File

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

View File

@@ -113,6 +113,28 @@ public partial class ManagementTask : AsyncDisposableObject
.ConfigurePlugins(a =>
{
a.UseTcpSessionCheckClear();
a.UseReconnection<TcpDmtpClient>(options =>
{
options.TryCount = -1;
options.PollingInterval = TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval);
options.PrintLog = true;
options.CheckAction = async (c, count) =>
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
if ((await c.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess)
{
return ConnectionCheckResult.Alive;
}
else
{
return ConnectionCheckResult.Dead;
}
};
});
a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b =>
{
b.UseSystemTextJson(json =>
@@ -131,10 +153,6 @@ public partial class ManagementTask : AsyncDisposableObject
a.Add<FilePlugin>();
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
.SetMaxFailCount(3);
a.AddDmtpCreatedChannelPlugin(async () =>
{
try
@@ -194,9 +212,6 @@ public partial class ManagementTask : AsyncDisposableObject
a.Add<FilePlugin>();
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
.SetMaxFailCount(3);
});
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);

View File

@@ -345,7 +345,25 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
.ConfigurePlugins(a =>
{
a.UseTcpSessionCheckClear();
a.UseReconnection<TcpDmtpClient>(options =>
{
options.TryCount = -1;
options.PollingInterval = TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval);
options.PrintLog = true;
options.CheckAction = async (c, count) =>
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
if ((await c.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess)
{
return ConnectionCheckResult.Alive;
}
else
{
return ConnectionCheckResult.Dead;
}
};
});
a.UseDmtpRpc(a => a.ConfigureDefaultSerializationSelector(b =>
{
b.UseSystemTextJson(json =>
@@ -358,9 +376,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
json.Converters.Add(new JArraySystemTextJsonConverter());
});
}));
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
.SetMaxFailCount(redundancy.MaxErrorCount);
});
@@ -405,9 +421,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
json.Converters.Add(new JArraySystemTextJsonConverter());
});
}));
a.UseDmtpHeartbeat()//使用Dmtp心跳
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
.SetMaxFailCount(redundancy.MaxErrorCount);
});
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);

View File

@@ -26,7 +26,7 @@ internal static class RuntimeServiceHelper
public static async Task InitAsync(List<ChannelRuntime> newChannelRuntimes, List<DeviceRuntime> newDeviceRuntimes, ILogger logger)
{
//批量修改之后,需要重新加载通道
foreach (var newChannelRuntime in newChannelRuntimes)
await newChannelRuntimes.ParallelForEachAsync(async (newChannelRuntime, token) =>
{
try
{
@@ -44,7 +44,7 @@ internal static class RuntimeServiceHelper
{
logger.LogWarning(ex, "Init Channel");
}
}
}).ConfigureAwait(false);
GlobalData.ChannelDeviceRuntimeDispatchService.Dispatch(null);
GlobalData.VariableRuntimeDispatchService.Dispatch(null);
}
@@ -71,28 +71,28 @@ internal static class RuntimeServiceHelper
public static async Task InitAsync(List<DeviceRuntime> newDeviceRuntimes, ILogger logger)
{
foreach (var newDeviceRuntime in newDeviceRuntimes)
{
try
await newDeviceRuntimes.ParallelForEachAsync(async (newDeviceRuntime, token) =>
{
if (GlobalData.IdChannels.TryGetValue(newDeviceRuntime.ChannelId, out var newChannelRuntime))
try
{
newDeviceRuntime.Init(newChannelRuntime);
if (GlobalData.IdChannels.TryGetValue(newDeviceRuntime.ChannelId, out var newChannelRuntime))
{
newDeviceRuntime.Init(newChannelRuntime);
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptEnumerableVariableRuntime();
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptEnumerableVariableRuntime();
newVariableRuntimes.ParallelForEach(item => item.Init(newDeviceRuntime));
newVariableRuntimes.ParallelForEach(item => item.Init(newDeviceRuntime));
}
else
{
logger.LogWarning("Channel not found");
}
}
else
catch (Exception ex)
{
logger.LogWarning("Channel not found");
logger.LogWarning(ex, "Init Device");
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Init Device");
}
}
}).ConfigureAwait(false);
GlobalData.ChannelDeviceRuntimeDispatchService.Dispatch(null);
GlobalData.VariableRuntimeDispatchService.Dispatch(null);
@@ -234,16 +234,16 @@ internal static class RuntimeServiceHelper
public static async Task RestartDeviceAsync(List<DeviceRuntime> newDeviceRuntimes)
{
var groups = GlobalData.GetDeviceThreadManages(newDeviceRuntimes);
foreach (var group in groups)
await groups.ParallelForEachAsync(async (group, token) =>
{
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.Value, false).ConfigureAwait(false);
}
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
}).ConfigureAwait(false);
await GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage).ParallelForEachAsync(async (group, token) =>
{
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
}
}).ConfigureAwait(false);
}
public static async Task RemoveDeviceAsync(HashSet<long> newDeciceIds)
{
@@ -254,17 +254,17 @@ internal static class RuntimeServiceHelper
public static async Task RemoveDeviceAsync(IEnumerable<DeviceRuntime> deviceRuntimes)
{
var groups = GlobalData.GetDeviceThreadManages(deviceRuntimes);
foreach (var group in groups)
{
if (group.Key != null)
await group.Key.RemoveDeviceAsync(group.Value.Select(a => a.Id).ToArray()).ConfigureAwait(false);
}
await groups.ParallelForEachAsync(async (group, token) =>
{
if (group.Key != null)
await group.Key.RemoveDeviceAsync(group.Value.Select(a => a.Id).ToArray()).ConfigureAwait(false);
}).ConfigureAwait(false);
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !deviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
await GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !deviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage).ParallelForEachAsync(async (group, token) =>
{
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
}
}).ConfigureAwait(false);
}

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
@inherits ComponentBase
@namespace ThingsGateway.Server
<div class="popup-overlay">
<div class="popup-window text-align: center; ">
<p>
🎉 <strong>ThingsGateway</strong> 正在参加
<strong> Gitee 2025 最受欢迎的开源软件评选活动 </strong>
需要你的支持!
</p>
<a href="https://gitee.com/activity/2025opensource?ident=I4XWR9"
target="_blank"
rel="noopener noreferrer"
class="popup-link" onclick="@OnClick">
👉 前往投票支持
</a>
</div>
</div>

View File

@@ -0,0 +1,28 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
using Microsoft.AspNetCore.Components;
namespace ThingsGateway.Server;
public partial class Gitee2025opensource
{
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
private async Task OnClick()
{
if (OnCloseAsync != null)
{
await OnCloseAsync();
}
}
}

View File

@@ -0,0 +1,43 @@
/* 弹窗遮罩 */
.popup-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(10, 10, 10, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
/* 弹窗主体 */
.popup-window {
background: #fff;
border-radius: 12px;
padding: 30px 40px;
max-width: 420px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);
text-align: center;
animation: popup-in 0.3s ease-out;
}
/* 按钮与链接 */
.popup-link {
display: inline-block;
margin-top: 10px;
margin-right: 20px; /* ✅ 按钮之间留空隙 */
margin-bottom: 20px; /* ✅ 按钮之间留空隙 */
background-color: #e4405f;
color: white;
padding: 8px 16px;
border-radius: 8px;
text-decoration: none;
font-weight: bold;
transition: background 0.2s;
}
.popup-link:hover {
background-color: #c8324f;
}

View File

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

View File

@@ -239,4 +239,33 @@ public partial class MainLayout : IDisposable
};
await DialogService.Show(op);
}
/// <summary>
/// 显示投票弹窗
/// </summary>
/// <returns></returns>
public async Task ShowGitee()
{
await DialogService.Show(new DialogOption()
{
IsScrolling = false,
ShowFooter = false,
Title = "Gitee 评选活动",
BodyTemplate = BootstrapDynamicComponent.CreateComponent<Gitee2025opensource>().Render(),
ShowCloseButton = false,
ShowHeaderCloseButton = false,
Size = Size.Small,
});
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (WebsiteOption.Value.Demo)
{
await ShowGitee();
}
await base.OnAfterRenderAsync(firstRender);
}
}

View File

@@ -41,7 +41,7 @@ public class Program
{
}
}
};

View File

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

View File

@@ -10,6 +10,8 @@
using Microsoft.AspNetCore.Components.Server.Circuits;
using System.Runtime;
using ThingsGateway.Common;
namespace ThingsGateway.Server;
@@ -22,6 +24,12 @@ public class ConnectionLimiterCircuitHandler : CircuitHandler
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
{
//主动触发垃圾回收,释放上个链路资源
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
GC.WaitForPendingFinalizers();
GC.Collect(1, GCCollectionMode.Optimized, blocking: false, compacting: false);
WebsiteOptions ??= App.GetOptions<WebsiteOptions>();
if (!WebsiteOptions.BlazorConnectionLimitEnable)

View File

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