Compare commits

...

5 Commits

Author SHA1 Message Date
Diego
6510c3e289 feat: 增加一个变量读写表达式常用转换的友好编辑界面 2025-06-24 16:14:35 +08:00
Diego
920e407d05 恢复规则引擎脚本接口 2025-06-24 10:52:00 +08:00
Diego
7314c8901d fix: 用户编辑框初始刷新职位 2025-06-24 09:15:33 +08:00
Diego
4e9f02b48c 更新依赖包 2025-06-24 09:03:34 +08:00
2248356998 qq.com
9ae7602cb4 配置最大连接数 2025-06-24 00:09:07 +08:00
22 changed files with 431 additions and 24 deletions

View File

@@ -39,6 +39,7 @@ public partial class SysUserEdit
var items = await SysPositionService.SelectorAsync(new PositionSelectorInput() { });
Items = PositionUtil.BuildCascaderItemList(items);
ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
await InvokeAsync(StateHasChanged);
await base.OnInitializedAsync();
}

View File

@@ -15,6 +15,7 @@ using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Log;
namespace ThingsGateway.Logging;
@@ -33,7 +34,7 @@ public sealed class DatabaseLoggerProvider : ILoggerProvider, ISupportExternalSc
/// <summary>
/// 日志消息队列(线程安全)
/// </summary>
private readonly BlockingCollection<LogMessage> _logMessageQueue = new(12000);
private readonly BlockingCollection<LogMessage> _logMessageQueue = new(20000);
/// <summary>
/// 日志作用域提供器
@@ -135,7 +136,10 @@ public sealed class DatabaseLoggerProvider : ILoggerProvider, ISupportExternalSc
{
try
{
_logMessageQueue.Add(logMsg);
if (!_logMessageQueue.TryAdd(logMsg, 5000))
{
XTrace.Log.Warn($"{nameof(DatabaseLoggerProvider)} queue add fail");
}
return;
}
catch (InvalidOperationException) { }

View File

@@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Log;
namespace ThingsGateway.Logging;
@@ -32,7 +33,7 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope
/// <summary>
/// 日志消息队列(线程安全)
/// </summary>
private readonly BlockingCollection<LogMessage> _logMessageQueue = new(12000);
private readonly BlockingCollection<LogMessage> _logMessageQueue = new(20000);
/// <summary>
/// 日志作用域提供器
@@ -170,8 +171,10 @@ public sealed class FileLoggerProvider : ILoggerProvider, ISupportExternalScope
{
try
{
_logMessageQueue.Add(logMsg);
return;
if (!_logMessageQueue.TryAdd(logMsg, 5000))
{
XTrace.Log.Warn($"{nameof(DatabaseLoggerProvider)} queue add fail");
}
}
catch (InvalidOperationException) { }
catch { }

View File

@@ -76,7 +76,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
/// <summary>
/// 作业持久化记录消息队列(线程安全)
/// </summary>
private readonly BlockingCollection<PersistenceContext> _persistenceMessageQueue = new(12000);
private readonly BlockingCollection<PersistenceContext> _persistenceMessageQueue = new(20000);
/// <summary>
/// 不受控的作业 Id 集合

View File

@@ -18,8 +18,6 @@ namespace ThingsGateway.Extension.Generic;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class GenericExtensions
{
private static MemoryCache Instance { get; set; } = new MemoryCache();
/// <summary>
/// 把已修改的属性赋值到列表中,并返回字典
/// </summary>

View File

@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor" Version="9.7.4-beta09" />
<PackageReference Include="BootstrapBlazor" Version="9.7.4" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<PluginVersion>10.8.19</PluginVersion>
<ProPluginVersion>10.8.19</ProPluginVersion>
<PluginVersion>10.8.23</PluginVersion>
<ProPluginVersion>10.8.23</ProPluginVersion>
<AuthenticationVersion>2.8.4</AuthenticationVersion>
<SourceGeneratorVersion>10.8.6</SourceGeneratorVersion>
<NET8Version>8.0.17</NET8Version>

View File

@@ -85,6 +85,7 @@ public static class ChannelOptionsExtensions
channelOptions.ThrowIfNull(nameof(IChannelOptions));
var channelType = channelOptions.ChannelType;
channelType.ThrowIfNull(nameof(ChannelTypeEnum));
config.SetMaxCount(channelOptions.MaxClientCount);
switch (channelType)
{
case ChannelTypeEnum.TcpClient:

View File

@@ -10,8 +10,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" />
<PackageReference Include="TouchSocket" Version="3.1.9" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.9" />
<PackageReference Include="TouchSocket" Version="3.1.10" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.10" />
</ItemGroup>
<ItemGroup>

View File

@@ -104,19 +104,20 @@ public class ExecuteScriptNode : TextNode, IActuatorNode, IExexcuteExpressionsBa
}
}
Task<OperResult<NodeOutput>> IActuatorNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
async Task<OperResult<NodeOutput>> IActuatorNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
{
try
{
Logger?.Trace($"Execute script");
var exexcuteExpressions = CSharpScriptEngineExtension.Do<IExexcuteExpressions>(Text);
exexcuteExpressions.Logger = Logger;
return exexcuteExpressions.ExecuteAsync(input, cancellationToken);
var data = await exexcuteExpressions.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
return new OperResult<NodeOutput>() { Content = data };
}
catch (Exception ex)
{
Logger?.LogWarning(ex);
return Task.FromResult(new OperResult<NodeOutput>(ex));
return new OperResult<NodeOutput>(ex);
}

View File

@@ -30,6 +30,6 @@ public interface IExexcuteExpressionsBase
public interface IExexcuteExpressions : IExexcuteExpressionsBase
{
public TouchSocket.Core.ILog Logger { get; set; }
Task<OperResult<NodeOutput>> ExecuteAsync(NodeInput input, CancellationToken cancellationToken);
Task<NodeOutput> ExecuteAsync(NodeInput input, CancellationToken cancellationToken);
}

View File

@@ -8,8 +8,8 @@
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.9" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.9" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.10" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.10" />
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
<!--<ProjectReference Include="..\..\PluginPro\ThingsGateway.Authentication\ThingsGateway.Authentication.csproj" />-->

View File

@@ -1,4 +1,22 @@
{
"ThingsGateway.Gateway.Razor.ValueTransformType": {
"None": "None",
"Linear": "Linear",
"Sqrt": "Sqrt"
},
"ThingsGateway.Gateway.Razor.ValueTransformConfig": {
"TransformType": "TransformType",
"MinMax": "MinMax",
"ClampToRawRange": "ClampToRawRange",
"DecimalPlaces": "DecimalPlaces",
"RawMin": "RawMin",
"RawMax": "RawMax",
"ActualMin": "ActualMin",
"ActualMax": "ActualMax"
},
"ThingsGateway.Management.Authentication": {
"AuthName": "AuthName",
"Authorized": "Authorized",

View File

@@ -1,4 +1,20 @@
{
"ThingsGateway.Gateway.Razor.ValueTransformType": {
"None": "无",
"Linear": "线性",
"Sqrt": "开方"
},
"ThingsGateway.Gateway.Razor.ValueTransformConfig": {
"TransformType": "转换方式",
"MinMax": "最小最大值",
"ClampToRawRange": "限制范围",
"DecimalPlaces": "保留小数位",
"RawMin": "原始最小值",
"RawMax": "原始最大值",
"ActualMin": "实际最小值",
"ActualMax": "实际最大值"
},
"ThingsGateway.Management.Authentication": {
"AuthName": "公司名称",
"Authorized": "已授权",

View File

@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
public class ValueTransformConfig
{
/// <summary>
/// 保留小数位
/// </summary>
public int DecimalPlaces { get; set; } = 2;
/// <summary>
/// 限制范围
/// </summary>
public bool ClampToRawRange { get; set; }
public ValueTransformType TransformType { get; set; }
/// <summary>
/// 原始低
/// </summary>
public decimal RawMin { get; set; }
/// <summary>
/// 原始高
/// </summary>
public decimal RawMax { get; set; }
/// <summary>
/// 实际低
/// </summary>
public decimal ActualMin { get; set; }
/// <summary>
/// 实际高
/// </summary>
public decimal ActualMax { get; set; }
}
public enum ValueTransformType
{
/// <summary>
/// 不转换,仅保留小数位
/// </summary>
None,
/// <summary>
/// 线性转换
/// </summary>
Linear,
/// <summary>
/// 开方转换
/// </summary>
Sqrt
}

View File

@@ -0,0 +1,31 @@
@namespace ThingsGateway.Gateway.Razor
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Foundation
@using ThingsGateway.Gateway.Application
@inherits ComponentDefault
<ValidateForm class="p-4 h-100" Model="@ValueTransformConfig" OnValidSubmit="OnSave">
<EditorForm AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=1 LabelWidth=150 Model="ValueTransformConfig">
<FieldItems>
<EditorItem @bind-Field="@context.TransformType" GroupName=@(ValueTransformConfigLocalizer["TransformType"]) Cols="4" />
<EditorItem @bind-Field="@context.ClampToRawRange" GroupName=@(ValueTransformConfigLocalizer["TransformType"]) Cols="4" />
<EditorItem @bind-Field="@context.DecimalPlaces" GroupName=@(ValueTransformConfigLocalizer["TransformType"]) Cols="4" />
<EditorItem @bind-Field="@context.RawMin" GroupName=@(ValueTransformConfigLocalizer["MinMax"]) GroupOrder=2 Cols="6" />
<EditorItem @bind-Field="@context.RawMax" GroupName=@(ValueTransformConfigLocalizer["MinMax"]) GroupOrder=2 Cols="6" />
<EditorItem @bind-Field="@context.ActualMin" GroupName=@(ValueTransformConfigLocalizer["MinMax"]) GroupOrder=2 Cols="6" />
<EditorItem @bind-Field="@context.ActualMax" GroupName=@(ValueTransformConfigLocalizer["MinMax"]) GroupOrder=2 Cols="6" />
</FieldItems>
<Buttons>
<Button ButtonType="ButtonType.Submit" Icon="fa-solid fa-floppy-disk" IsAsync Text=@RazorLocalizer["Save"] />
</Buttons>
</EditorForm>
</ValidateForm>

View File

@@ -0,0 +1,215 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components.Forms;
using System.Globalization;
using System.Text.RegularExpressions;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Razor;
public partial class ValueTransformConfigPage
{
private ValueTransformConfig ValueTransformConfig = new();
[Inject]
[NotNull]
private IStringLocalizer<ValueTransformConfig>? ValueTransformConfigLocalizer { get; set; }
[Parameter]
public string Expressions { get; set; }
[Parameter]
public EventCallback<string> ExpressionsChanged { get; set; }
public static bool TryParseLinearFormula(string formula, out ValueTransformConfig config)
{
config = new ValueTransformConfig();
var dec = @"[\d]+(?:\.[\d]+)?";
try
{
// None + clamp actual
var m = Regex.Match(formula, $@"^Math\.Round\(\s*
Math\.Min\(\s*
Math\.Max\(\s*
raw\.ToDecimal\(\)\s*,\s*({dec})\s*\)\s*,\s*({dec})\s*\)\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
if (m.Success)
{
config.TransformType = ValueTransformType.None;
config.ClampToRawRange = true;
config.ActualMin = decimal.Parse(m.Groups[1].Value);
config.ActualMax = decimal.Parse(m.Groups[2].Value);
config.DecimalPlaces = int.Parse(m.Groups[3].Value);
return true;
}
// None pure
m = Regex.Match(formula, $@"^Math\.Round\(\s*raw\.ToDecimal\(\)\s*,\s*(\d+)\s*\)$");
if (m.Success)
{
config.TransformType = ValueTransformType.None;
config.ClampToRawRange = false;
config.DecimalPlaces = int.Parse(m.Groups[1].Value);
return true;
}
// Linear + clamp actual
m = Regex.Match(formula, $@"^Math\.Round\(\s*
Math\.Min\(\s*
Math\.Max\(\s*
\(\(raw\.ToDecimal\(\)\s*-\s*({dec})\)\s*/\s*
\(({dec})\s*-\s*({dec})\)\s*\*\s*
\(({dec})\s*-\s*({dec})\)\s*\+\s*
({dec})\)\s*,\s*({dec})\)\s*,\s*({dec})\)\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
if (m.Success)
{
config.TransformType = ValueTransformType.Linear;
config.ClampToRawRange = true;
config.RawMin = decimal.Parse(m.Groups[1].Value);
config.RawMax = decimal.Parse(m.Groups[2].Value);
config.ActualMax = decimal.Parse(m.Groups[4].Value);
config.ActualMin = decimal.Parse(m.Groups[5].Value);
config.DecimalPlaces = int.Parse(m.Groups[9].Value);
return true;
}
// Linear pure
m = Regex.Match(formula, $@"^Math\.Round\(\s*
\(\(raw\.ToDecimal\(\)\s*-\s*({dec})\)\s*/\s*
\(({dec})\s*-\s*({dec})\)\s*\*\s*
\(({dec})\s*-\s*({dec})\)\s*\+\s*({dec})\)\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
if (m.Success)
{
config.TransformType = ValueTransformType.Linear;
config.ClampToRawRange = false;
config.RawMin = decimal.Parse(m.Groups[1].Value); // raw减数0
config.RawMax = decimal.Parse(m.Groups[2].Value); // 分母第一个数10
config.ActualMax = decimal.Parse(m.Groups[4].Value); // 乘数第一个数1
config.ActualMin = decimal.Parse(m.Groups[6].Value); // 加数0
config.DecimalPlaces = int.Parse(m.Groups[7].Value); // 小数位2
return true;
}
// Sqrt + clamp actual
m = Regex.Match(formula, $@"^Math\.Round\(\s*
Math\.Min\(\s*
Math\.Max\(\s*
Math\.Sqrt\(Math\.Max\(raw\.ToDecimal\(\),\s*0\)\)\s*\*\s*({dec})\s*,\s*({dec})\)\s*,\s*({dec})\)\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
if (m.Success)
{
config.TransformType = ValueTransformType.Sqrt;
config.ClampToRawRange = true;
config.ActualMax = decimal.Parse(m.Groups[1].Value);
config.ActualMin = decimal.Parse(m.Groups[2].Value);
config.DecimalPlaces = int.Parse(m.Groups[4].Value);
return true;
}
// Sqrt pure
m = Regex.Match(formula, $@"^Math\.Round\(\s*
Math\.Sqrt\(Math\.Max\(raw\.ToDecimal\(\),\s*0\)\)\s*\*\s*({dec})\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
if (m.Success)
{
config.TransformType = ValueTransformType.Sqrt;
config.ClampToRawRange = false;
config.DecimalPlaces = int.Parse(m.Groups[2].Value);
return true;
}
}
catch
{
// ignore
}
return false;
}
public static string GenerateFormula(ValueTransformConfig config)
{
// 只有 ClampToRawRange 为 true 时才包裹实际值范围限制
string clampActual(string expr)
{
if (config.ClampToRawRange)
return $"Math.Min(Math.Max({expr}, {config.ActualMin}), {config.ActualMax})";
else
return expr;
}
string rawExpr = "raw.ToDecimal()"; // 这里不做 raw clamp
switch (config.TransformType)
{
case ValueTransformType.None:
return $"Math.Round({clampActual(rawExpr)}, {config.DecimalPlaces})";
case ValueTransformType.Linear:
var linearExpr = $"(({rawExpr} - {config.RawMin}) / ({config.RawMax} - {config.RawMin}) * ({config.ActualMax} - {config.ActualMin}) + {config.ActualMin})";
return $"Math.Round({clampActual(linearExpr)}, {config.DecimalPlaces})";
case ValueTransformType.Sqrt:
var sqrtExpr = $"Math.Sqrt(Math.Max({rawExpr}, 0)) * {config.ActualMax}";
return $"Math.Round({clampActual(sqrtExpr)}, {config.DecimalPlaces})";
default:
throw new NotSupportedException($"Unsupported transform type: {config.TransformType}");
}
}
protected override void OnParametersSet()
{
if (!Expressions.IsNullOrWhiteSpace())
{
if (TryParseLinearFormula(Expressions, out var config))
{
ValueTransformConfig = config;
}
}
base.OnParametersSet();
}
#region
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
private async Task OnSave(EditContext editContext)
{
try
{
var result = GenerateFormula(ValueTransformConfig);
Expressions = result;
if (ExpressionsChanged.HasDelegate)
{
await ExpressionsChanged.InvokeAsync(result);
}
else
{
Expressions = result;
}
if (OnCloseAsync != null)
await OnCloseAsync();
}
catch (Exception ex)
{
await ToastService.Warn(ex);
}
}
#endregion
}

View File

@@ -0,0 +1,7 @@
.appconfig ::deep .tabs-body-content {
height: 100% !important;
}
.appconfig {
height: 100% !important;
}

View File

@@ -113,8 +113,28 @@
</EditorItem>
<EditorItem @bind-Field="@context.ArrayLength" />
<EditorItem @bind-Field="@context.ReadExpressions" Rows="1" />
<EditorItem @bind-Field="@context.WriteExpressions" Rows="1" />
<EditorItem @bind-Field="@context.ReadExpressions">
<EditTemplate Context="value">
<div class="col-12">
<BootstrapInputGroup>
<Textarea rows="1" @bind-Value="value.ReadExpressions" ShowLabel="true"></Textarea>
<Button Icon="fa-solid fa-bars" OnClick="() => ShowExpressionsUI(true)"></Button>
</BootstrapInputGroup>
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.WriteExpressions">
<EditTemplate Context="value">
<div class="col-12">
<BootstrapInputGroup>
<Textarea rows="1" @bind-Value="value.WriteExpressions" ShowLabel="true"></Textarea>
<Button Icon="fa-solid fa-bars" OnClick="() => ShowExpressionsUI(false)"></Button>
</BootstrapInputGroup>
</div>
</EditTemplate>
</EditorItem>
<EditorItem TValue="string" TModel="Variable" @bind-Field="@context.Description">

View File

@@ -163,7 +163,37 @@ public partial class VariableEditComponent
op.Component = AddressDynamicComponent;
await DialogService.Show(op);
}
[Inject]
IStringLocalizer<Variable> VariableLocalizer { get; set; }
private async Task ShowExpressionsUI(bool read)
{
var op = new DialogOption()
{
IsScrolling = false,
ShowMaximizeButton = true,
Size = Size.Large,
Title = $"{Model.Name} {(read ? VariableLocalizer[nameof(Variable.ReadExpressions)] : VariableLocalizer[nameof(Variable.WriteExpressions)])}",
ShowFooter = false,
ShowCloseButton = false,
BodyTemplate = BootstrapDynamicComponent.CreateComponent<ValueTransformConfigPage>(new Dictionary<string, object?>
{
{nameof(ValueTransformConfigPage.ExpressionsChanged), EventCallback.Factory.Create<string>(this,a =>
{
if(read)
{
Model.ReadExpressions = a;
}
else
{
Model.WriteExpressions=a;
}
})
},
{nameof(ValueTransformConfigPage.Expressions),read?Model.ReadExpressions:Model.WriteExpressions },
}).Render(),
};
await DialogService.Show(op);
}
[Inject]
private IStringLocalizer<Device> DeviceLocalizer { get; set; }

View File

@@ -16,7 +16,7 @@
<ItemGroup>
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.9" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.10" />
</ItemGroup>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>10.8.19</Version>
<Version>10.8.23</Version>
</PropertyGroup>
<ItemGroup>