mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 18:51:28 +08:00
Compare commits
9 Commits
10.11.96.0
...
c0337e2b19
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c0337e2b19 | ||
![]() |
8a95f48f5a | ||
![]() |
14f3c31265 | ||
![]() |
1bad65378f | ||
![]() |
db3affc67e | ||
![]() |
5ee8b50a92 | ||
![]() |
301beda2a2 | ||
![]() |
628b51a353 | ||
![]() |
f03445bc83 |
@@ -251,11 +251,13 @@ public class RequestAuditFilter : IAsyncActionFilter, IOrderedFilter
|
||||
|
||||
if (exception == null)
|
||||
{
|
||||
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
logger.Log(LogLevel.Information, $"{logData.Method}:{logData.Path}-{logData.Operation}");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}");
|
||||
if (logger.IsEnabled(LogLevel.Warning))
|
||||
logger.Log(LogLevel.Warning, $"{logData.Method}:{logData.Path}-{logData.Operation}{Environment.NewLine}{logData.Exception?.ToSystemTextJsonString()}{Environment.NewLine}{logData.Validation?.ToSystemTextJsonString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<div class="tg-table h-100">
|
||||
|
||||
<Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
||||
DataService="DataService" CreateItemCallback="CreateItemCallback!"
|
||||
DataService="DataService" CreateItemCallback="CreateItemCallback!" RenderMode=RenderMode
|
||||
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!"
|
||||
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender
|
||||
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
|
||||
@@ -41,6 +41,7 @@
|
||||
DoubleClickToEdit="DoubleClickToEdit"
|
||||
OnDoubleClickCellCallback="OnDoubleClickCellCallback"
|
||||
OnDoubleClickRowCallback="OnDoubleClickRowCallback"
|
||||
RowContentTemplate="RowContentTemplate"
|
||||
OnClickRowCallback="OnClickRowCallback">
|
||||
</Table>
|
||||
</div>
|
||||
|
@@ -13,6 +13,14 @@ namespace ThingsGateway.Admin.Razor;
|
||||
[CascadingTypeParameter(nameof(TItem))]
|
||||
public partial class AdminTable<TItem> where TItem : class, new()
|
||||
{
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.RenderMode"/>
|
||||
[Parameter]
|
||||
public TableRenderMode RenderMode { get; set; }
|
||||
|
||||
public List<ITableColumn> Columns => Instance?.Columns;
|
||||
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
|
||||
@@ -40,6 +48,10 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
||||
/// <inheritdoc cref="Table{TItem}.OnDoubleClickRowCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, Task>? OnDoubleClickRowCallback { get; set; }
|
||||
/// <inheritdoc cref="Table{TItem}.RowContentTemplate"/>
|
||||
[Parameter]
|
||||
public RenderFragment<TableRowContext<TItem>>? RowContentTemplate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Table{TItem}.OnClickRowCallback"/>
|
||||
[Parameter]
|
||||
public Func<TItem, Task>? OnClickRowCallback { get; set; }
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.1" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.3" />
|
||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
|
||||
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
|
||||
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" />
|
||||
|
@@ -66,7 +66,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (Logger?.IsEnabled(LogLevel.Error) == true)
|
||||
Logger.LogError(ex, "{JsonStringLocalizerName} searched for '{Name}' in '{typeName}' with culture '{CultureName}' throw exception.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -176,7 +177,8 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (jsonLocalizationOptions.IgnoreLocalizerMissing == false)
|
||||
{
|
||||
Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
if (Logger?.IsEnabled(LogLevel.Information) == true)
|
||||
Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
|
||||
}
|
||||
_missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}");
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.1" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -295,7 +295,8 @@ internal sealed class EventBusHostedService : BackgroundService
|
||||
, retryAction: (total, times) =>
|
||||
{
|
||||
// 输出重试日志
|
||||
_logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId);
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true)
|
||||
_logger.LogWarning("Retrying {times}/{total} times for {EventId}", times, total, eventSource.EventId);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
|
@@ -369,11 +369,13 @@ internal sealed class ScheduleHostedService : BackgroundService
|
||||
// 写入作业执行详细日志
|
||||
if (executionException == null)
|
||||
{
|
||||
jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext);
|
||||
if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true)
|
||||
jobLogger?.LogInformation("{jobExecutingContext}", jobExecutingContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext);
|
||||
if (jobLogger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error) == true)
|
||||
jobLogger?.LogError(executionException, "{jobExecutingContext}", jobExecutingContext);
|
||||
}
|
||||
|
||||
// 记录作业触发器运行信息
|
||||
|
@@ -251,7 +251,8 @@ public sealed class ProfilerDelegatingHandler(ILogger<Logging> logger, IOptions<
|
||||
// 检查是否配置(注册)了日志程序
|
||||
if (remoteOptions.IsLoggingRegistered)
|
||||
{
|
||||
logger.Log(remoteOptions.ProfilerLogLevel, "{message}", message);
|
||||
if (logger?.IsEnabled(remoteOptions.ProfilerLogLevel) == true)
|
||||
logger.Log(remoteOptions.ProfilerLogLevel, "{message}", message);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -1,34 +1,108 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
public class ExpiringDictionary<TKey, TValue> : IDisposable
|
||||
{
|
||||
private ConcurrentDictionary<TKey, TValue> _dict = new();
|
||||
private readonly TimerX _cleanupTimer;
|
||||
|
||||
public ExpiringDictionary(int cleanupInterval = 60000)
|
||||
/// <summary>缓存项</summary>
|
||||
public class CacheItem
|
||||
{
|
||||
_cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true };
|
||||
private TValue? _value;
|
||||
/// <summary>数值</summary>
|
||||
public TValue? Value { get => _value; }
|
||||
|
||||
/// <summary>过期时间。系统启动以来的毫秒数</summary>
|
||||
public Int64 ExpiredTime { get; set; }
|
||||
|
||||
/// <summary>是否过期</summary>
|
||||
public Boolean Expired => ExpiredTime <= Runtime.TickCount64;
|
||||
|
||||
/// <summary>访问时间</summary>
|
||||
public Int64 VisitTime { get; private set; }
|
||||
|
||||
/// <summary>构造缓存项</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="expire"></param>
|
||||
public CacheItem(TValue? value, Int32 expire) => Set(value, expire);
|
||||
|
||||
/// <summary>设置数值和过期时间</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="expire">过期时间,秒</param>
|
||||
public void Set(TValue value, Int32 expire)
|
||||
{
|
||||
_value = value;
|
||||
|
||||
var now = VisitTime = Runtime.TickCount64;
|
||||
if (expire <= 0)
|
||||
ExpiredTime = Int64.MaxValue;
|
||||
else
|
||||
ExpiredTime = now + expire * 1000L;
|
||||
}
|
||||
|
||||
/// <summary>更新访问时间并返回数值</summary>
|
||||
/// <returns></returns>
|
||||
public TValue? Visit()
|
||||
{
|
||||
VisitTime = Runtime.TickCount64;
|
||||
var rs = _value;
|
||||
if (rs == null) return default;
|
||||
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
private ConcurrentDictionary<TKey, CacheItem> _dict;
|
||||
|
||||
private readonly TimerX _cleanupTimer;
|
||||
private int defaultExpire = 60;
|
||||
IEqualityComparer<TKey>? comparer;
|
||||
public ExpiringDictionary(int expire = 60, IEqualityComparer<TKey>? comparer = null)
|
||||
{
|
||||
defaultExpire = expire;
|
||||
this.comparer = comparer;
|
||||
_dict = new ConcurrentDictionary<TKey, CacheItem>(comparer);
|
||||
|
||||
_cleanupTimer = new TimerX(TimerClear, null, 10000, 10000) { Async = true };
|
||||
}
|
||||
|
||||
public void TryAdd(TKey key, TValue value)
|
||||
public bool TryAdd(TKey key, TValue value)
|
||||
{
|
||||
_dict.TryAdd(key, value);
|
||||
if (_dict.TryGetValue(key, out var item))
|
||||
{
|
||||
if (!item.Expired) return false;
|
||||
item.Set(value, defaultExpire);
|
||||
return true;
|
||||
}
|
||||
return _dict.TryAdd(key, new CacheItem(value, defaultExpire));
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return _dict.TryGetValue(key, out value);
|
||||
value = default;
|
||||
|
||||
// 没有值,直接结束
|
||||
if (!_dict.TryGetValue(key, out var item) || item == null) return false;
|
||||
|
||||
// 得到已有值
|
||||
value = item.Visit();
|
||||
|
||||
// 是否未过期的有效值
|
||||
return !item.Expired;
|
||||
}
|
||||
public TValue GetOrAdd(TKey key, Func<TKey, TValue> func)
|
||||
{
|
||||
return _dict.GetOrAdd(key, func);
|
||||
}
|
||||
public TValue GetOrAdd(TKey key, TValue value)
|
||||
{
|
||||
return _dict.GetOrAdd(key, value);
|
||||
CacheItem? item = null;
|
||||
do
|
||||
{
|
||||
if (_dict.TryGetValue(key, out item) && item != null) return item.Visit();
|
||||
|
||||
item ??= new CacheItem(func(key), defaultExpire);
|
||||
}
|
||||
while (!_dict.TryAdd(key, item));
|
||||
|
||||
return item.Visit();
|
||||
|
||||
}
|
||||
|
||||
public bool TryRemove(TKey key) => _dict.TryRemove(key, out _);
|
||||
@@ -38,13 +112,38 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
|
||||
private void Clear(object? state)
|
||||
{
|
||||
var data = _dict;
|
||||
_dict = new();
|
||||
_dict = new(comparer);
|
||||
data.Clear();
|
||||
}
|
||||
private void TimerClear(object? state)
|
||||
{
|
||||
|
||||
var dic = _dict;
|
||||
if (dic.IsEmpty) return;
|
||||
|
||||
// 60分钟之内过期的数据,进入LRU淘汰
|
||||
var now = Runtime.TickCount64;
|
||||
|
||||
// 这里先计算,性能很重要
|
||||
var toDels = new List<TKey>();
|
||||
foreach (var item in dic)
|
||||
{
|
||||
// 已过期,准备删除
|
||||
var ci = item.Value;
|
||||
if (ci.ExpiredTime <= now)
|
||||
toDels.Add(item.Key);
|
||||
}
|
||||
|
||||
// 确认删除
|
||||
foreach (var item in toDels)
|
||||
{
|
||||
_dict.Remove(item);
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_dict.Clear();
|
||||
_cleanupTimer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.NewLife.Log;
|
||||
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
/// <summary>
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
namespace ThingsGateway;
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@@ -561,7 +561,7 @@ public static class Reflect
|
||||
/// <param name="method"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
public static TFunc? As<TFunc>(this MethodInfo method, object? target = null)
|
||||
public static TFunc? As<TFunc>(this MethodInfo method, object? target = null) where TFunc : class
|
||||
{
|
||||
if (method == null) return default;
|
||||
|
||||
@@ -569,10 +569,14 @@ public static class Reflect
|
||||
|
||||
var func = DelegateCache<TFunc>.Cache.GetOrAdd(
|
||||
key,
|
||||
_ => (TFunc)(object)(
|
||||
_ =>
|
||||
{
|
||||
return (
|
||||
target == null
|
||||
? Delegate.CreateDelegate(typeof(TFunc), method, true)
|
||||
: Delegate.CreateDelegate(typeof(TFunc), target, method, true)));
|
||||
? Delegate.CreateDelegate(typeof(TFunc), method, true) as TFunc
|
||||
: Delegate.CreateDelegate(typeof(TFunc), target, method, true) as TFunc
|
||||
);
|
||||
});
|
||||
|
||||
return func;
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<Import Project="..\..\PackNuget.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net462;netstandard2.0;net6.0;net6.0-windows;net8.0;$(OtherTargetFrameworks);net8.0-windows;</TargetFrameworks>
|
||||
<TargetFrameworks>net47;netstandard2.0;net6.0;net6.0-windows;net8.0;$(OtherTargetFrameworks);net8.0-windows;</TargetFrameworks>
|
||||
<AssemblyName>ThingsGateway.NewLife.X</AssemblyName>
|
||||
<RootNamespace>ThingsGateway.NewLife</RootNamespace>
|
||||
<AssemblyTitle>工具核心库</AssemblyTitle>
|
||||
@@ -35,12 +35,11 @@
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||
<PackageReference Include="System.Memory" Version="4.6.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net47'">
|
||||
<PackageReference Include="System.Memory" Version="4.6.3" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net47'">
|
||||
<Using Include="System.Net.Http" />
|
||||
<Reference Include="Microsoft.VisualBasic" />
|
||||
<Reference Include="System.Management" />
|
||||
|
@@ -1,7 +1,6 @@
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.NewLife.Log;
|
||||
using ThingsGateway.NewLife.Reflection;
|
||||
|
||||
namespace ThingsGateway.NewLife.Threading;
|
||||
|
||||
@@ -391,11 +390,7 @@ public class TimerX : ITimer, ITimerx, IDisposable
|
||||
// 释放非托管资源
|
||||
Scheduler?.Remove(this, disposing ? "Dispose" : "GC");
|
||||
|
||||
DelegateCache<TimerCallback>.Cache.Clear();
|
||||
#if NET6_0_OR_GREATER
|
||||
DelegateCache<Func<Object?, ValueTask>>.Cache.Clear();
|
||||
#endif
|
||||
DelegateCache<Func<Object?, Task>>.Cache.Clear();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@@ -57,6 +57,7 @@ namespace ThingsGateway.SqlSugar
|
||||
}
|
||||
});
|
||||
|
||||
_systemTextJsonSettings.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
_systemTextJsonSettings.Converters.Add(new JTokenSystemTextJsonConverter());
|
||||
_systemTextJsonSettings.Converters.Add(new JValueSystemTextJsonConverter());
|
||||
_systemTextJsonSettings.Converters.Add(new JObjectSystemTextJsonConverter());
|
||||
|
@@ -31,8 +31,8 @@
|
||||
<PackageReference Include="Npgsql" Version="9.0.4" />
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||
<PackageReference Include="TDengine.Connector" Version="3.1.9" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.27" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.26.0" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.28" />
|
||||
<PackageReference Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
|
@@ -1,18 +1,18 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.11.96</PluginVersion>
|
||||
<ProPluginVersion>10.11.96</ProPluginVersion>
|
||||
<DefaultVersion>10.11.96</DefaultVersion>
|
||||
<PluginVersion>10.11.105</PluginVersion>
|
||||
<ProPluginVersion>10.11.105</ProPluginVersion>
|
||||
<DefaultVersion>10.11.105</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.20</NET8Version>
|
||||
<NET10Version>10.0.0-rc.1.25451.107</NET10Version>
|
||||
<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.112</TSVersion>
|
||||
<TSVersion>4.0.0-beta.120</TSVersion>
|
||||
|
||||
|
||||
</PropertyGroup>
|
||||
|
@@ -73,7 +73,7 @@ public interface IChannelOptions
|
||||
bool RtsEnable { get; set; }
|
||||
|
||||
bool StreamAsync { get; set; }
|
||||
|
||||
|
||||
Handshake Handshake { get; set; }
|
||||
#endregion
|
||||
/// <summary>
|
||||
|
@@ -553,9 +553,9 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
return waitData.CompletedData;
|
||||
|
||||
var reusableTimeout = _reusableTimeouts.Get();
|
||||
var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, Channel.ClosedToken);
|
||||
try
|
||||
{
|
||||
var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, Channel.ClosedToken);
|
||||
|
||||
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
|
||||
@@ -583,7 +583,9 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
else
|
||||
{
|
||||
var operResult = waitData.Check(reusableTimeout.TimeoutStatus);
|
||||
return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
|
||||
waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}";
|
||||
return waitData.CompletedData;
|
||||
//return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@@ -100,7 +100,7 @@ public static class LoggerExtensions
|
||||
/// </summary>
|
||||
public static string GetDeviceLogBasePath()
|
||||
{
|
||||
return PathExtensions.CombinePathWithOs("Logs","DeviceLog");
|
||||
return PathExtensions.CombinePathWithOs("Logs", "DeviceLog");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -143,7 +143,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
|
||||
[SugarColumn(ColumnDescription = "StreamAsync", IsNullable = true)]
|
||||
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
|
||||
public override bool StreamAsync { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handshake
|
||||
/// </summary>
|
||||
|
@@ -117,6 +117,7 @@ public partial class ManagementTask : AsyncDisposableObject
|
||||
{
|
||||
b.UseSystemTextJson(json =>
|
||||
{
|
||||
json.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
json.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
|
||||
json.Converters.Add(new JTokenSystemTextJsonConverter());
|
||||
json.Converters.Add(new JValueSystemTextJsonConverter());
|
||||
@@ -180,6 +181,7 @@ public partial class ManagementTask : AsyncDisposableObject
|
||||
{
|
||||
b.UseSystemTextJson(json =>
|
||||
{
|
||||
json.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
json.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
|
||||
json.Converters.Add(new JTokenSystemTextJsonConverter());
|
||||
json.Converters.Add(new JValueSystemTextJsonConverter());
|
||||
|
@@ -350,6 +350,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
{
|
||||
b.UseSystemTextJson(json =>
|
||||
{
|
||||
json.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
json.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
|
||||
json.Converters.Add(new JTokenSystemTextJsonConverter());
|
||||
json.Converters.Add(new JValueSystemTextJsonConverter());
|
||||
@@ -394,6 +395,9 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
{
|
||||
b.UseSystemTextJson(json =>
|
||||
{
|
||||
|
||||
|
||||
json.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
json.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
|
||||
json.Converters.Add(new JTokenSystemTextJsonConverter());
|
||||
json.Converters.Add(new JValueSystemTextJsonConverter());
|
||||
|
@@ -136,7 +136,8 @@ internal sealed class PluginService : IPluginService
|
||||
if (driverType != null)
|
||||
{
|
||||
var driver = (DriverBase)Activator.CreateInstance(driverType);
|
||||
_logger?.LogInformation(string.Format(AppResource.LoadTypeSuccess, pluginName));
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true)
|
||||
_logger?.LogInformation(string.Format(AppResource.LoadTypeSuccess, pluginName));
|
||||
_driverBaseDict.TryAdd(pluginName, driverType);
|
||||
return driver;
|
||||
}
|
||||
@@ -440,7 +441,8 @@ internal sealed class PluginService : IPluginService
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 加载失败时记录警告信息
|
||||
_logger?.LogWarning(ex, string.Format(AppResource.LoadOtherFileFail, item));
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true)
|
||||
_logger?.LogWarning(ex, string.Format(AppResource.LoadOtherFileFail, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -679,7 +681,8 @@ internal sealed class PluginService : IPluginService
|
||||
}
|
||||
//添加到全局对象
|
||||
_assemblyLoadContextDict.TryAdd(fileName, (assemblyLoadContext, assembly));
|
||||
_logger?.LogInformation(string.Format(AppResource.AddPluginFile, path));
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true)
|
||||
_logger?.LogInformation(string.Format(AppResource.AddPluginFile, path));
|
||||
}
|
||||
return assembly;
|
||||
}
|
||||
@@ -728,7 +731,8 @@ internal sealed class PluginService : IPluginService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, string.Format(AppResource.LoadOtherFileFail, item));
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true)
|
||||
_logger?.LogWarning(ex, string.Format(AppResource.LoadOtherFileFail, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -827,7 +831,8 @@ internal sealed class PluginService : IPluginService
|
||||
{
|
||||
//添加到字典
|
||||
_driverBaseDict.TryAdd($"{driverMainName}.{type.Name}", type);
|
||||
_logger?.LogInformation(string.Format(AppResource.LoadTypeSuccess, PluginInfoUtil.GetFullName(driverMainName, type.Name)));
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information) == true)
|
||||
_logger?.LogInformation(string.Format(AppResource.LoadTypeSuccess, PluginInfoUtil.GetFullName(driverMainName, type.Name)));
|
||||
}
|
||||
var plugin = new PluginInfo()
|
||||
{
|
||||
@@ -847,7 +852,8 @@ internal sealed class PluginService : IPluginService
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录加载插件失败的日志
|
||||
_logger?.LogWarning(ex, string.Format(AppResource.LoadPluginFail, Path.GetRelativePath(AppContext.BaseDirectory.CombinePathWithOs(tempDir), folderPath)));
|
||||
if (_logger?.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning) == true)
|
||||
_logger?.LogWarning(ex, string.Format(AppResource.LoadPluginFail, Path.GetRelativePath(AppContext.BaseDirectory.CombinePathWithOs(tempDir), folderPath)));
|
||||
}
|
||||
}
|
||||
return plugins.DistinctBy(a => a.FullName).OrderBy(a => a.EducationPlugin);
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" />
|
||||
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />-->
|
||||
<PackageReference Include="TouchSocket.WebApi" Version="$(TSVersion)" />
|
||||
|
@@ -0,0 +1,19 @@
|
||||
@namespace ThingsGateway.Gateway.Razor
|
||||
@using System.Text.Json.Nodes
|
||||
@using Microsoft.Extensions.Hosting
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@inherits ComponentDefault
|
||||
|
||||
|
||||
@foreach (var col in RowContent.Columns)
|
||||
{
|
||||
<td class="@GetFixedCellClassString(col)" style="@GetFixedCellStyleString(col)">
|
||||
<DynamicElement TagName="div" TriggerClick="@false"
|
||||
StopPropagation="false"
|
||||
class="@GetCellClassString(col, false, false)">
|
||||
@GetValue(col, RowContent.Row)
|
||||
</DynamicElement>
|
||||
</td>
|
||||
}
|
@@ -0,0 +1,279 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public partial class VariableRow : IDisposable
|
||||
{
|
||||
[Parameter]
|
||||
public TableRowContext<VariableRuntime>? RowContent { get; set; }
|
||||
private bool Disposed;
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
timer?.SafeDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
TimerX? timer;
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
timer = new TimerX(Refresh, null, 1000, 1000, "VariableRow");
|
||||
}
|
||||
base.OnAfterRender(firstRender);
|
||||
}
|
||||
|
||||
private Task Refresh(object? state)
|
||||
{
|
||||
if (!Disposed)
|
||||
return InvokeAsync(StateHasChanged);
|
||||
else
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
FixedCellClassStringCache?.Clear();
|
||||
CellClassStringCache?.Clear();
|
||||
base.OnParametersSet();
|
||||
}
|
||||
/// <summary>
|
||||
/// 获得 指定单元格数据方法
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
protected static RenderFragment GetValue(ITableColumn col, VariableRuntime item) => builder =>
|
||||
{
|
||||
if (col.Template != null)
|
||||
{
|
||||
builder.AddContent(0, col.Template(item));
|
||||
}
|
||||
else if (col.ComponentType == typeof(ColorPicker))
|
||||
{
|
||||
// 自动化处理 ColorPicker 组件
|
||||
builder.AddContent(10, col.RenderColor(item));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddContent(20, col.RenderValue(item));
|
||||
}
|
||||
};
|
||||
|
||||
// internal static string? GetDoubleClickCellClassString(bool trigger) => CssBuilder.Default()
|
||||
//.AddClass("is-dbcell", trigger)
|
||||
//.Build();
|
||||
|
||||
/// <summary>
|
||||
/// 获得指定列头固定列样式
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="margin"></param>
|
||||
/// <returns></returns>
|
||||
protected string? GetFixedCellStyleString(ITableColumn col, int margin = 0)
|
||||
{
|
||||
string? ret = null;
|
||||
if (col.Fixed)
|
||||
{
|
||||
ret = IsTail(col) ? GetRightStyle(col, margin) : GetLeftStyle(col);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private string? GetLeftStyle(ITableColumn col)
|
||||
{
|
||||
var columns = RowContent.Columns.ToList();
|
||||
var defaultWidth = 200;
|
||||
var width = 0;
|
||||
var start = 0;
|
||||
var index = columns.IndexOf(col);
|
||||
//if (GetFixedDetailRowHeaderColumn)
|
||||
//{
|
||||
// width += DetailColumnWidth;
|
||||
//}
|
||||
//if (GetFixedMultipleSelectColumn)
|
||||
//{
|
||||
// width += MultiColumnWidth;
|
||||
//}
|
||||
if (GetFixedLineNoColumn)
|
||||
{
|
||||
width += LineNoColumnWidth;
|
||||
}
|
||||
while (index > start)
|
||||
{
|
||||
var column = columns[start++];
|
||||
width += column.Width ?? defaultWidth;
|
||||
}
|
||||
return $"left: {width}px;";
|
||||
}
|
||||
private bool GetFixedLineNoColumn = false;
|
||||
|
||||
private string? GetRightStyle(ITableColumn col, int margin)
|
||||
{
|
||||
var columns = RowContent.Columns.ToList();
|
||||
var defaultWidth = 200;
|
||||
var width = 0;
|
||||
var index = columns.IndexOf(col);
|
||||
|
||||
// after
|
||||
while (index + 1 < columns.Count)
|
||||
{
|
||||
var column = columns[index++];
|
||||
width += column.Width ?? defaultWidth;
|
||||
}
|
||||
//if (ShowExtendButtons && FixedExtendButtonsColumn)
|
||||
{
|
||||
width += ExtendButtonColumnWidth;
|
||||
}
|
||||
|
||||
// 如果是固定表头时增加滚动条位置
|
||||
if (IsFixedHeader && (index + 1) == columns.Count)
|
||||
{
|
||||
width += margin;
|
||||
}
|
||||
return $"right: {width}px;";
|
||||
}
|
||||
private bool IsFixedHeader = true;
|
||||
|
||||
public int LineNoColumnWidth { get; set; } = 60;
|
||||
public int ExtendButtonColumnWidth { get; set; } = 220;
|
||||
|
||||
|
||||
private bool IsTail(ITableColumn col)
|
||||
{
|
||||
var middle = Math.Floor(RowContent.Columns.Count() * 1.0 / 2);
|
||||
var index = Columns.IndexOf(col);
|
||||
return middle < index;
|
||||
}
|
||||
private ConcurrentDictionary<ITableColumn, string> CellClassStringCache { get; } = new(ReferenceEqualityComparer.Instance);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 Cell 文字样式
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="hasChildren"></param>
|
||||
/// <param name="inCell"></param>
|
||||
/// <returns></returns>
|
||||
protected string? GetCellClassString(ITableColumn col, bool hasChildren, bool inCell)
|
||||
{
|
||||
if (CellClassStringCache.TryGetValue(col, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool trigger = false;
|
||||
return CellClassStringCache.GetOrAdd(col, col => CssBuilder.Default("table-cell")
|
||||
.AddClass(col.GetAlign().ToDescriptionString(), col.Align == Alignment.Center || col.Align == Alignment.Right)
|
||||
.AddClass("is-wrap", col.GetTextWrap())
|
||||
.AddClass("is-ellips", col.GetTextEllipsis())
|
||||
.AddClass("is-tips", col.GetShowTips())
|
||||
.AddClass("is-resizable", AllowResizing)
|
||||
.AddClass("is-tree", IsTree && hasChildren)
|
||||
.AddClass("is-incell", inCell)
|
||||
.AddClass("is-dbcell", trigger)
|
||||
.AddClass(col.CssClass)
|
||||
.Build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool AllowResizing = true;
|
||||
private bool IsTree = false;
|
||||
|
||||
private ConcurrentDictionary<ITableColumn, string> FixedCellClassStringCache { get; } = new(ReferenceEqualityComparer.Instance);
|
||||
/// <summary>
|
||||
/// 获得指定列头固定列样式
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <returns></returns>
|
||||
protected string? GetFixedCellClassString(ITableColumn col)
|
||||
{
|
||||
if (FixedCellClassStringCache.TryGetValue(col, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FixedCellClassStringCache.GetOrAdd(col, col => CssBuilder.Default()
|
||||
.AddClass("fixed", col.Fixed)
|
||||
.AddClass("fixed-right", col.Fixed && IsTail(col))
|
||||
.AddClass("fr", IsLastColumn(col))
|
||||
.AddClass("fl", IsFirstColumn(col))
|
||||
.Build());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Parameter]
|
||||
public Func<List<ITableColumn>> ColumnsFunc { get; set; }
|
||||
public List<ITableColumn> Columns => ColumnsFunc();
|
||||
|
||||
private ConcurrentDictionary<ITableColumn, bool> LastFixedColumnCache { get; } = new(ReferenceEqualityComparer.Instance);
|
||||
private bool IsLastColumn(ITableColumn col)
|
||||
{
|
||||
if (LastFixedColumnCache.TryGetValue(col, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
return LastFixedColumnCache.GetOrAdd(col, col =>
|
||||
{
|
||||
var ret = false;
|
||||
if (col.Fixed && !IsTail(col))
|
||||
{
|
||||
var index = Columns.IndexOf(col) + 1;
|
||||
ret = index < Columns.Count && Columns[index].Fixed == false;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
private ConcurrentDictionary<ITableColumn, bool> FirstFixedColumnCache { get; } = new(ReferenceEqualityComparer.Instance);
|
||||
private bool IsFirstColumn(ITableColumn col)
|
||||
{
|
||||
if (FirstFixedColumnCache.TryGetValue(col, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FirstFixedColumnCache.GetOrAdd(col, col =>
|
||||
{
|
||||
var ret = false;
|
||||
if (col.Fixed && IsTail(col))
|
||||
{
|
||||
// 查找前一列是否固定
|
||||
var index = Columns.IndexOf(col) - 1;
|
||||
if (index > 0)
|
||||
{
|
||||
ret = !Columns[index].Fixed;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
internal static class VariableRowHelpers
|
||||
{
|
||||
internal static Alignment GetAlign(this ITableColumn col) => col.Align ?? Alignment.None;
|
||||
internal static bool GetTextWrap(this ITableColumn col) => col.TextWrap ?? false;
|
||||
internal static bool GetShowTips(this ITableColumn col) => col.ShowTips ?? false;
|
||||
|
||||
|
||||
internal static RenderFragment RenderColor<TItem>(this ITableColumn col, TItem item) => builder =>
|
||||
{
|
||||
var val = GetItemValue(col, item);
|
||||
var v = val?.ToString() ?? "#000";
|
||||
var style = $"background-color: {v};";
|
||||
builder.OpenElement(0, "div");
|
||||
builder.AddAttribute(1, "class", "is-color");
|
||||
builder.AddAttribute(2, "style", style);
|
||||
builder.CloseElement();
|
||||
};
|
||||
internal static object? GetItemValue<TItem>(this ITableColumn col, TItem item)
|
||||
{
|
||||
var fieldName = col.GetFieldName();
|
||||
object? ret;
|
||||
if (item is IDynamicObject dynamicObject)
|
||||
{
|
||||
ret = dynamicObject.GetValue(fieldName);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = Utility.GetPropertyValue<TItem, object?>(item, fieldName);
|
||||
|
||||
if (ret != null)
|
||||
{
|
||||
var t = ret.GetType();
|
||||
if (t.IsEnum)
|
||||
{
|
||||
// 如果是枚举这里返回 枚举的描述信息
|
||||
var itemName = ret.ToString();
|
||||
if (!string.IsNullOrEmpty(itemName))
|
||||
{
|
||||
ret = Utility.GetDisplayName(t, itemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
internal static bool GetTextEllipsis(this ITableColumn col) => col.TextEllipsis ?? false;
|
||||
|
||||
}
|
@@ -15,13 +15,13 @@
|
||||
AllowResizing="true"
|
||||
OnAdd="OnAdd"
|
||||
IsFixedHeader=true
|
||||
ShowCardView=false
|
||||
IsMultipleSelect=true
|
||||
SearchMode=SearchMode.Top
|
||||
ShowExtendButtons=true
|
||||
ShowToolbar="true"
|
||||
ShowExportButton
|
||||
IsAutoRefresh
|
||||
AutoRefreshInterval="2000"
|
||||
RenderMode="TableRenderMode.Table"
|
||||
ShowDefaultButtons=true
|
||||
ShowSearch=false
|
||||
ExtendButtonColumnWidth=220
|
||||
@@ -85,6 +85,11 @@
|
||||
<VariableEditComponent Model=@(context) AutoRestartThread="AutoRestartThread"></VariableEditComponent>
|
||||
</EditTemplate>
|
||||
|
||||
<RowContentTemplate Context="context">
|
||||
|
||||
<VariableRow RowContent="@context" ColumnsFunc="ColumnsFunc"></VariableRow>
|
||||
|
||||
</RowContentTemplate>
|
||||
|
||||
<ExportButtonDropdownTemplate Context="ExportContext">
|
||||
@if ((AuthorizeButton("导出")))
|
||||
|
@@ -15,13 +15,17 @@ using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Admin.Razor;
|
||||
using ThingsGateway.DB;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public partial class VariableRuntimeInfo : IDisposable
|
||||
{
|
||||
public List<ITableColumn> ColumnsFunc()
|
||||
{
|
||||
return table?.Columns;
|
||||
}
|
||||
|
||||
#if !Management
|
||||
[Parameter]
|
||||
public ChannelDeviceTreeItem SelectModel { get; set; }
|
||||
@@ -55,11 +59,11 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(1000));
|
||||
|
||||
#if !Management
|
||||
_ = RunTimerAsync();
|
||||
//timer = new TimerX(RunTimerAsync, null, 1000, 1000) { Async = true };
|
||||
#endif
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
//private TimerX timer;
|
||||
/// <summary>
|
||||
/// IntFormatter
|
||||
/// </summary>
|
||||
@@ -84,6 +88,12 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
base.OnParametersSet();
|
||||
scheduler?.Trigger();
|
||||
}
|
||||
|
||||
private async Task Notify(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
@@ -92,27 +102,23 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
}
|
||||
|
||||
private async Task RunTimerAsync()
|
||||
{
|
||||
while (!Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
//if (table != null)
|
||||
// await table.QueryAsync();
|
||||
//private async Task RunTimerAsync(object? state)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// //if (table != null)
|
||||
// // await InvokeAsync(() => table.RowElementRefresh());
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
// await InvokeAsync(StateHasChanged);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// NewLife.Log.XTrace.WriteException(ex);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// }
|
||||
//}
|
||||
|
||||
#region 查询
|
||||
|
||||
@@ -126,7 +132,7 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
return data;
|
||||
#else
|
||||
var data = Items
|
||||
.WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText))
|
||||
.WhereIf(!string.IsNullOrWhiteSpace(options.SearchText), a => a.Name.Contains(options.SearchText))
|
||||
.GetQueryData(options);
|
||||
_option = options;
|
||||
return Task.FromResult(data);
|
||||
@@ -354,7 +360,7 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
#if !Management
|
||||
|
||||
var models = Items
|
||||
.WhereIf(!_option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(_option.SearchText)).GetData(_option, out var total).Cast<Variable>().ToList();
|
||||
.WhereIf(!string.IsNullOrWhiteSpace(_option.SearchText), a => a.Name.Contains(_option.SearchText)).GetData(_option, out var total).Cast<Variable>().ToList();
|
||||
|
||||
#else
|
||||
|
||||
|
@@ -516,185 +516,169 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||
protected override Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||
{
|
||||
var requestInfo = e.RequestInfo;
|
||||
bool modbusRtu = false;
|
||||
ModbusRequest modbusRequest = default;
|
||||
ReadOnlySequence<byte> readOnlySequences = default;
|
||||
//接收外部报文
|
||||
if (requestInfo is ModbusRtuSlaveMessage modbusRtuSlaveMessage)
|
||||
return HandleChannelReceivedAsync(client, e, last);
|
||||
}
|
||||
|
||||
private async Task HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||
{
|
||||
if (!TryParseRequest(e.RequestInfo, out var modbusRequest, out var sequences, out var modbusRtu))
|
||||
return;
|
||||
|
||||
if (!MulStation && modbusRequest.Station != Station)
|
||||
return;
|
||||
|
||||
var function = NormalizeFunctionCode(modbusRequest.FunctionCode);
|
||||
|
||||
if (function <= 4)
|
||||
await HandleReadRequestAsync(client, e, modbusRequest, sequences, modbusRtu).ConfigureAwait(false);
|
||||
else
|
||||
await HandleWriteRequestAsync(client, e, modbusRequest, sequences, modbusRtu, function).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool TryParseRequest(object requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu)
|
||||
{
|
||||
modbusRequest = default;
|
||||
sequences = default;
|
||||
modbusRtu = false;
|
||||
|
||||
switch (requestInfo)
|
||||
{
|
||||
if (!modbusRtuSlaveMessage.IsSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
modbusRequest = modbusRtuSlaveMessage.Request;
|
||||
readOnlySequences = modbusRtuSlaveMessage.Sequences;
|
||||
modbusRtu = true;
|
||||
case ModbusRtuSlaveMessage rtuMsg when rtuMsg.IsSuccess:
|
||||
modbusRequest = rtuMsg.Request;
|
||||
sequences = rtuMsg.Sequences;
|
||||
modbusRtu = true;
|
||||
return true;
|
||||
|
||||
case ModbusTcpSlaveMessage tcpMsg when tcpMsg.IsSuccess:
|
||||
modbusRequest = tcpMsg.Request;
|
||||
sequences = tcpMsg.Sequences;
|
||||
modbusRtu = false;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
else if (requestInfo is ModbusTcpSlaveMessage modbusTcpSlaveMessage)
|
||||
}
|
||||
|
||||
private static byte NormalizeFunctionCode(byte funcCode)
|
||||
=> funcCode > 0x30 ? (byte)(funcCode - 0x30) : funcCode;
|
||||
|
||||
private async Task HandleReadRequestAsync(
|
||||
IClientChannel client,
|
||||
ReceivedDataEventArgs e,
|
||||
ModbusRequest modbusRequest,
|
||||
ReadOnlySequence<byte> sequences,
|
||||
bool modbusRtu)
|
||||
{
|
||||
var data = ModbusRequest(modbusRequest, true);
|
||||
if (!data.IsSuccess)
|
||||
{
|
||||
if (!modbusTcpSlaveMessage.IsSuccess)
|
||||
{
|
||||
await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ValueByteBlock byteBlock = new(1024);
|
||||
try
|
||||
{
|
||||
WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu);
|
||||
await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleWriteRequestAsync(
|
||||
IClientChannel client,
|
||||
ReceivedDataEventArgs e,
|
||||
ModbusRequest modbusRequest,
|
||||
ReadOnlySequence<byte> sequences,
|
||||
bool modbusRtu,
|
||||
byte f)
|
||||
{
|
||||
var modbusAddress = new ModbusAddress(modbusRequest);
|
||||
bool isSuccess;
|
||||
|
||||
switch (f)
|
||||
{
|
||||
case 5:
|
||||
case 15:
|
||||
modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode;
|
||||
modbusAddress.FunctionCode = 1;
|
||||
isSuccess = await HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
case 16:
|
||||
modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode;
|
||||
modbusAddress.FunctionCode = 3;
|
||||
isSuccess = await HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
modbusRequest = modbusTcpSlaveMessage.Request;
|
||||
readOnlySequences = modbusTcpSlaveMessage.Sequences;
|
||||
modbusRtu = false;
|
||||
}
|
||||
|
||||
if (isSuccess)
|
||||
await WriteSuccess(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
||||
else
|
||||
await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<bool> HandleWriteCoreAsync(ModbusAddress address, IClientChannel client, ModbusRequest modbusRequest)
|
||||
{
|
||||
if (WriteData != null)
|
||||
{
|
||||
var result = await WriteData(address, ThingsGatewayBitConverter, client).ConfigureAwait(false);
|
||||
if (!result.IsSuccess) return false;
|
||||
}
|
||||
|
||||
if (IsWriteMemory)
|
||||
{
|
||||
var memResult = ModbusRequest(modbusRequest, false);
|
||||
return memResult.IsSuccess;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void WriteReadResponse(
|
||||
ModbusRequest modbusRequest,
|
||||
ReadOnlySequence<byte> sequences,
|
||||
ReadOnlyMemory<byte> content,
|
||||
ref ValueByteBlock byteBlock,
|
||||
bool modbusRtu)
|
||||
{
|
||||
if (modbusRtu)
|
||||
ByteBlockExtension.Write(ref byteBlock, sequences.Slice(0, 2));
|
||||
else
|
||||
ByteBlockExtension.Write(ref byteBlock, sequences.Slice(0, 8));
|
||||
|
||||
if (modbusRequest.IsBitFunction)
|
||||
{
|
||||
var bitdata = content.Span.ByteToBool().AsSpan().BoolArrayToByte();
|
||||
var len = (int)Math.Ceiling(modbusRequest.Length / 8.0);
|
||||
var bitWriteData = bitdata.AsMemory().Slice(0, len);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)bitWriteData.Length);
|
||||
byteBlock.Write(bitWriteData.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)content.Length);
|
||||
byteBlock.Write(content.Span);
|
||||
}
|
||||
//忽略不同设备地址的报文
|
||||
if (!MulStation && modbusRequest.Station != Station)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var f = modbusRequest.FunctionCode > 0x30 ? modbusRequest.FunctionCode - 0x30 : modbusRequest.FunctionCode;
|
||||
|
||||
if (f <= 4)
|
||||
{
|
||||
var data = ModbusRequest(modbusRequest, true);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
ValueByteBlock byteBlock = new(1024);
|
||||
try
|
||||
{
|
||||
if (modbusRtu)
|
||||
{
|
||||
ByteBlockExtension.Write(ref byteBlock, readOnlySequences.Slice(0, 2));
|
||||
if (modbusRequest.IsBitFunction)
|
||||
{
|
||||
var bitdata = data.Content.Span.ByteToBool().AsSpan().BoolArrayToByte();
|
||||
ReadOnlyMemory<byte> bitwritedata = bitdata.Length == (int)Math.Ceiling(modbusRequest.Length / 8.0) ? bitdata.AsMemory() : bitdata.AsMemory().Slice(0, (int)Math.Ceiling(modbusRequest.Length / 8.0));
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)bitwritedata.Length);
|
||||
byteBlock.Write(bitwritedata.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)data.Content.Length);
|
||||
byteBlock.Write(data.Content.Span);
|
||||
}
|
||||
byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Memory.Span));
|
||||
await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBlockExtension.Write(ref byteBlock, readOnlySequences.Slice(0, 8));
|
||||
if (modbusRequest.IsBitFunction)
|
||||
{
|
||||
var bitdata = data.Content.Span.ByteToBool().AsSpan().BoolArrayToByte();
|
||||
ReadOnlyMemory<byte> bitwritedata = bitdata.Length == (int)Math.Ceiling(modbusRequest.Length / 8.0) ? bitdata.AsMemory() : bitdata.AsMemory().Slice(0, (int)Math.Ceiling(modbusRequest.Length / 8.0));
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)bitwritedata.Length);
|
||||
byteBlock.Write(bitwritedata.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)data.Content.Length);
|
||||
byteBlock.Write(data.Content.Span);
|
||||
}
|
||||
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
|
||||
await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);//返回错误码
|
||||
}
|
||||
}
|
||||
else//写入
|
||||
{
|
||||
if (f == 5 || f == 15)
|
||||
{
|
||||
//写入继电器
|
||||
if (WriteData != null)
|
||||
{
|
||||
var modbusAddress = new ModbusAddress(modbusRequest) { WriteFunctionCode = modbusRequest.FunctionCode, FunctionCode = 1 };
|
||||
// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
if ((await WriteData(modbusAddress, ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess)
|
||||
{
|
||||
await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
if (IsWriteMemory)
|
||||
{
|
||||
var result = ModbusRequest(modbusRequest, false);
|
||||
if (result.IsSuccess)
|
||||
await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
else
|
||||
await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入内存区
|
||||
var result = ModbusRequest(modbusRequest, false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (f == 6 || f == 16)
|
||||
{
|
||||
//写入寄存器
|
||||
if (WriteData != null)
|
||||
{
|
||||
var modbusAddress = new ModbusAddress(modbusRequest) { WriteFunctionCode = modbusRequest.FunctionCode, FunctionCode = 3 };
|
||||
if ((await WriteData(modbusAddress, ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess)
|
||||
{
|
||||
if (IsWriteMemory)
|
||||
{
|
||||
var result = ModbusRequest(modbusRequest, false);
|
||||
if (result.IsSuccess)
|
||||
await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
else
|
||||
await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = ModbusRequest(modbusRequest, false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modbusRtu)
|
||||
byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Memory.Span));
|
||||
else
|
||||
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
|
||||
}
|
||||
|
||||
private async Task ReturnData(IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e)
|
||||
|
@@ -11,26 +11,7 @@ public class TestKafkaDynamicModel1 : DynamicModelBase
|
||||
|
||||
public TestKafkaDynamicModel1()
|
||||
{
|
||||
var name = "测试MqttServer";
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(name, out var kafka1))
|
||||
{
|
||||
id = kafka1.Id;
|
||||
|
||||
foreach (var item in kafka1.Driver?.IdVariableRuntimes)
|
||||
{
|
||||
//变量备注1作为Key(AE报警SourceId)
|
||||
var data1 = item.Value.GetPropertyValue(id, nameof(BusinessVariableProperty.Data1));
|
||||
if (!data1.IsNullOrEmpty())
|
||||
{
|
||||
variableRuntimes.Add(data1, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"找不到设备 {name}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +19,48 @@ public class TestKafkaDynamicModel1 : DynamicModelBase
|
||||
|
||||
public override IEnumerable<dynamic> GetList(IEnumerable<object> datas)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
|
||||
var name = "kafka_DA"; Logger?.LogInformation("进来了10000");
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(name, out var kafka1))
|
||||
{
|
||||
id = kafka1.Id;
|
||||
|
||||
if (kafka1.Driver != null)
|
||||
{
|
||||
foreach (var item in kafka1.Driver?.IdVariableRuntimes)
|
||||
{
|
||||
//变量备注1作为Key(AE报警SourceId)
|
||||
var data1 = item.Value.GetPropertyValue(id, nameof(BusinessVariableProperty.Data1));
|
||||
if (!data1.IsNullOrEmpty())
|
||||
{
|
||||
variableRuntimes.Add(data1, item.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in kafka1.ReadOnlyVariableRuntimes)
|
||||
{
|
||||
//变量备注1作为Key(AE报警SourceId)
|
||||
var data1 = item.Value.GetPropertyValue(id, nameof(BusinessVariableProperty.Data1));
|
||||
if (!data1.IsNullOrEmpty())
|
||||
{
|
||||
variableRuntimes.Add(data1, item.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"找不到设备 {name}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (datas == null) return null;
|
||||
var pluginEventDatas = datas.Cast<PluginEventData>();
|
||||
var opcDatas = pluginEventDatas.Select(
|
||||
@@ -67,13 +90,14 @@ public class TestKafkaDynamicModel1 : DynamicModelBase
|
||||
//重连时触发的事件,可以跳过不处理
|
||||
//if(opcAeEventData.Refresh)
|
||||
// continue;
|
||||
Logger?.LogInformation("进来了");
|
||||
var sourceName = opcAeEventData.SourceID;
|
||||
if (variableRuntimes.TryGetValue(sourceName, out var variableRuntime))
|
||||
{
|
||||
|
||||
var ack = opcAeEventData.EventType != Opc.Ae.EventType.Condition ? false : ((Opc.Ae.ConditionState)opcAeEventData.NewState).HasFlag(Opc.Ae.ConditionState.Acknowledged);
|
||||
|
||||
bool isRecover = opcAeEventData.EventType != Opc.Ae.EventType.Condition ? false : !((Opc.Ae.ConditionState)opcAeEventData.NewState).HasFlag(Opc.Ae.ConditionState.Active);
|
||||
|
||||
if (opcAeEventData.EventType != Opc.Ae.EventType.Condition)
|
||||
{
|
||||
bool alarm = (opcAeEventData.Message).Contains("raised");
|
||||
@@ -92,6 +116,71 @@ public class TestKafkaDynamicModel1 : DynamicModelBase
|
||||
isRecover = !alarm;
|
||||
}
|
||||
|
||||
|
||||
//判断报警类型
|
||||
string _alarmInfo = "";
|
||||
Logger.LogDebug($"opcAeEventData.ConditionName:{opcAeEventData.ConditionName}");
|
||||
Logger.LogDebug($"opcAeEventData.NewState:{opcAeEventData.NewState}");
|
||||
// 处理特定条件名称
|
||||
if (opcAeEventData.ConditionName == "CiAdvancedAlarmState" ||
|
||||
opcAeEventData.ConditionName == "CiDigitalAlarmState")
|
||||
{
|
||||
string _data8 = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data8));
|
||||
string[] data8Parts = _data8?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
|
||||
_alarmInfo = opcAeEventData.NewState switch
|
||||
{
|
||||
3 when data8Parts.Length > 0 => data8Parts[0],
|
||||
1 when data8Parts.Length > 1 => data8Parts[1],
|
||||
7 => "确认",
|
||||
_ => ""
|
||||
};
|
||||
Logger.LogDebug($"_alarmInfo:{_alarmInfo}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理确认状态
|
||||
if (opcAeEventData.NewState == 7)
|
||||
{
|
||||
_alarmInfo = "确认";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理其他状态
|
||||
_alarmInfo = string.IsNullOrEmpty(opcAeEventData.SubConditionName)
|
||||
? "恢复"
|
||||
: opcAeEventData.SubConditionName switch
|
||||
{
|
||||
"CiAnalogAlarmHighHigh" => "高高限报警",
|
||||
"CiAnalogAlarmHigh" => "高限报警",
|
||||
"CiAnalogAlarmLow" => "低限报警",
|
||||
"CiAnalogAlarmLowLow" => "低低限报警",
|
||||
"CiDigitalAlarmOn" => "合闸",
|
||||
"CiAdvancedAlarmOn" => "通讯中断",
|
||||
_ => "恢复" // 默认值
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//处理报警等级
|
||||
string _alarmLevel = opcAeEventData.Severity switch
|
||||
{
|
||||
997 => "1",
|
||||
993 => "2",
|
||||
989 => "3",
|
||||
985 => "4",
|
||||
_ => "0"
|
||||
};
|
||||
|
||||
//处理报警内容
|
||||
string content = "";
|
||||
string _msg = opcAeEventData.Message;
|
||||
string[] parts = _msg.Split('-', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
content = parts[1];
|
||||
}
|
||||
|
||||
//构建告警实体
|
||||
KafkaAlarmEntity alarmEntity = new KafkaAlarmEntity
|
||||
{
|
||||
@@ -100,14 +189,15 @@ public class TestKafkaDynamicModel1 : DynamicModelBase
|
||||
resourceName = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data4)), //资源名称
|
||||
metricCode = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data5)), //指标编码
|
||||
metricName = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data6)), //指标名称
|
||||
content = $"{variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data4))}{variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data6))}{opcAeEventData.Message}", //告警内容,设备名称+告警内容(包含阈值信息),可能opcae里没有带阈值信息,那么就需要录入固定值,可选Data10
|
||||
content = $"{variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data4))}{variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data6))}{content}_{_alarmInfo}", //告警内容,设备名称+告警内容(包含阈值信息),可能opcae里没有带阈值信息,那么就需要录入固定值,可选Data10
|
||||
alarmType = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data7)), // opcAeEventData.Severity 告警类型,子系统产生告警的类型,可能需要固定备注值
|
||||
|
||||
confirmedTime = ack ? opcAeEventData.Time.DateTimeToUnixTimestamp() : null, //告警确认时间
|
||||
fixTime = isRecover ? opcAeEventData.Time : null, //解除告警时间
|
||||
lastTime = opcAeEventData.AlarmTime, //产生告警时间
|
||||
status = isRecover ? "FIXED" : "UNFIXED", //告警状态
|
||||
alarmLevel = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data8)), //opcAeEventData.Severity.ToString(), //告警等级,可能需要固定备注值
|
||||
alarmLevel = _alarmLevel,
|
||||
// alarmLevel = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data8)), //opcAeEventData.Severity.ToString(), //告警等级,可能需要固定备注值
|
||||
subSystemCode = variableRuntime.GetPropertyValue(id, nameof(BusinessVariableProperty.Data9)), //子系统编码
|
||||
type = "SUB_SYSTEM_ALARM", //默认填写字段
|
||||
confirmAccount = opcAeEventData.ActorID, //告警确认人
|
||||
|
@@ -15,9 +15,6 @@ using BootstrapBlazor.Components;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
|
||||
namespace ThingsGateway.Server;
|
||||
@@ -28,8 +25,6 @@ public partial class GatewayIndexComponent : IDisposable
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
[NotNull]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public IStringLocalizer Localizer { get; set; }
|
||||
|
||||
private Chart AlarmPie { get; set; }
|
||||
@@ -44,7 +39,6 @@ public partial class GatewayIndexComponent : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
@@ -68,12 +62,15 @@ public partial class GatewayIndexComponent : IDisposable
|
||||
if (AlarmPieInit)
|
||||
await AlarmPie.Update(ChartAction.Update);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user