feat: 规则引擎 自定义执行脚本支持 `IDisposable` 接口

refactor: 脚本内日志对象统一命名为 ``Logger``
This commit is contained in:
Diego
2025-03-25 10:46:03 +08:00
parent 37b99cb1b0
commit bdc9eb44ec
23 changed files with 79 additions and 35 deletions

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<PluginVersion>10.2.3</PluginVersion>
<ProPluginVersion>10.2.3</ProPluginVersion>
<PluginVersion>10.2.4</PluginVersion>
<ProPluginVersion>10.2.4</ProPluginVersion>
</PropertyGroup>
<PropertyGroup>

View File

@@ -70,6 +70,10 @@ public static class CSharpScriptEngineExtension
}
}
public static void Remove(string source)
{
Instance.Remove($"{CacheKey}-{source}");
}
private static MemoryCache Instance { get; set; } = new MemoryCache();

View File

@@ -8,5 +8,5 @@ public abstract class TextNode : PlaceholderModel
}
[ModelValue]
public string Text { get; set; }
public virtual string Text { get; set; }
}

View File

@@ -1,5 +1,6 @@

using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife;
using TouchSocket.Core;
@@ -10,11 +11,40 @@ public class ExecuteScriptNode : TextNode, IActuatorNode, IExexcuteExpressionsBa
{
public ExecuteScriptNode(string id, Point? position = null) : base(id, position) { Title = "ExecuteScriptNode"; Placeholder = "ExecuteScriptNode.Placeholder"; }
private string text;
[ModelValue]
public override string Text
{
get
{
return text;
}
set
{
if (text != value)
{
try
{
var exexcuteExpressions = CSharpScriptEngineExtension.Do<IExexcuteExpressions>(text);
exexcuteExpressions.TryDispose();
}
catch
{
}
CSharpScriptEngineExtension.Remove(text);
}
text = value;
}
}
Task<NodeOutput> IActuatorNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
{
LogMessage?.Trace($"Execute script");
Logger?.Trace($"Execute script");
var exexcuteExpressions = CSharpScriptEngineExtension.Do<IExexcuteExpressions>(Text);
exexcuteExpressions.Logger = LogMessage;
exexcuteExpressions.Logger = Logger;
return exexcuteExpressions.ExecuteAsync(input, cancellationToken);
}

View File

@@ -19,13 +19,13 @@ public class VariableRpcNode : VariableNode, IActuatorNode
{
var data = await value.RpcAsync(input.JToken.ToString(), $"RulesEngine: {RulesEngineName}", cancellationToken).ConfigureAwait(false);
if (data.IsSuccess)
LogMessage?.Trace($" VariableRpcNode - VariableName {Text} : execute success");
Logger?.Trace($" VariableRpcNode - VariableName {Text} : execute success");
else
LogMessage?.Warning($" VariableRpcNode - VariableName {Text} : {data.ErrorMessage}");
Logger?.Warning($" VariableRpcNode - VariableName {Text} : {data.ErrorMessage}");
return new NodeOutput() { Value = data };
}
}
LogMessage?.Warning($" VariableRpcNode - VariableName {Text} : not found");
Logger?.Warning($" VariableRpcNode - VariableName {Text} : not found");
return new NodeOutput() { };
}

View File

@@ -11,5 +11,5 @@ public abstract class BaseNode : NodeModel, INode
}
public string RulesEngineName { get; set; }
public ILog LogMessage { get; set; }
public ILog Logger { get; set; }
}

View File

@@ -15,9 +15,9 @@ public class ConditionNode : TextNode, IConditionNode
Task<bool> IConditionNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
{
var value = Text.GetExpressionsResult(input.Value, LogMessage);
var value = Text.GetExpressionsResult(input.Value, Logger);
var next = value.ToBoolean(false);
LogMessage?.Trace($"Condition result: {next}");
Logger?.Trace($"Condition result: {next}");
return Task.FromResult(next);
}

View File

@@ -12,10 +12,10 @@ public class DataNode : TextNode, IExpressionNode
Task<NodeOutput> IExpressionNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
{
var value = Text.GetExpressionsResult(input.Value, LogMessage);
var value = Text.GetExpressionsResult(input.Value, Logger);
NodeOutput nodeOutput = new();
nodeOutput.Value = value;
LogMessage?.Trace($"Data result: {nodeOutput.JToken?.ToString(Newtonsoft.Json.Formatting.Indented)}");
Logger?.Trace($"Data result: {nodeOutput.JToken?.ToString(Newtonsoft.Json.Formatting.Indented)}");
return Task.FromResult(nodeOutput);
}
}

View File

@@ -9,7 +9,7 @@ public class DelayNode : NumberNode, IExpressionNode
async Task<NodeOutput> IExpressionNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
{
LogMessage?.Trace($"Delay {Number} ms");
Logger?.Trace($"Delay {Number} ms");
await Task.Delay(Number ?? 0, cancellationToken).ConfigureAwait(false);
return new NodeOutput();
}

View File

@@ -2,11 +2,20 @@
<div class="row me-4 script">
<div class="col-12 col-md-12">
<div class="col-12 col-md-8">
<BootstrapLabel Value=@Localizer["Script"] ShowLabelTooltip="true" />
<CodeEditor ShowLineNo Value=@Script ValueChanged=@Change Language="csharp" Theme="vs-dark" />
</div>
<div class="col-12 col-md-4">
<BootstrapLabel Value=@Localizer["Tip"] ShowLabelTooltip="true" />
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("注意 <code>raw</code> 参数 为 <code>object</code> 类型,需要转换为实际类型操作"))</Alert>
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("获取设备类实体,可用 <code>GlobalData.ReadOnlyDevices</code> 字典对象,键为设备名称,值为设备对象"))</Alert>
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("获取变量类实体,可用方法 <code>GlobalData.GetVariable(\"设备名称1\",\"变量名称1\")</code> "))</Alert>
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("详细说明查看文档对应内容页面"))</Alert>
</div>
<div class="col-6 col-md-6">
<BootstrapLabel Value=@Localizer["Input"] ShowLabelTooltip="true" />

View File

@@ -2,12 +2,14 @@
public interface INode
{
public TouchSocket.Core.ILog LogMessage { get; set; }
public TouchSocket.Core.ILog Logger { get; set; }
}
public interface IConditionNode : INode
{
public Task<bool> ExecuteAsync(NodeInput input, CancellationToken cancellationToken);
}
public interface IExpressionNode : INode
{
public Task<NodeOutput> ExecuteAsync(NodeInput input, CancellationToken cancellationToken);

View File

@@ -89,13 +89,13 @@ public class AlarmChangedTriggerNode : VariableNode, ITriggerNode, IDisposable
{
if (FuncDict.TryGetValue(item, out var func))
{
item.LogMessage?.Trace($"Alarm changed: {item.Text}");
item.Logger?.Trace($"Alarm changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = alarmVariable }).ConfigureAwait(false);
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
item.Logger?.LogWarning(ex);
}
}, Environment.ProcessorCount, token).ConfigureAwait(false);
}

View File

@@ -71,14 +71,14 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
{
if (FuncDict.TryGetValue(item, out var func))
{
item.LogMessage?.Trace($"Device changed: {item.Text}");
item.Logger?.Trace($"Device changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = deviceDatas }).ConfigureAwait(false);
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
item.Logger?.LogWarning(ex);
}
}, Environment.ProcessorCount, token).ConfigureAwait(false);
}

View File

@@ -36,14 +36,14 @@ public class TimeIntervalTriggerNode : TextNode, ITriggerNode, IDisposable
{
if (Func != null)
{
LogMessage?.Trace($"Timer: {Text}");
Logger?.Trace($"Timer: {Text}");
await Func.Invoke(new NodeOutput() { Value = TimeTick.LastTime }).ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex);
Logger?.LogWarning(ex);
}
finally
{

View File

@@ -80,14 +80,14 @@ public class ValueChangedTriggerNode : VariableNode, ITriggerNode, IDisposable
{
if (FuncDict.TryGetValue(item, out var func))
{
item.LogMessage?.Trace($"Variable changed: {item.Text}");
item.Logger?.Trace($"Variable changed: {item.Text}");
await func.Invoke(new NodeOutput() { Value = variableBasicData }).ConfigureAwait(false);
}
}
catch (Exception ex)
{
item.LogMessage?.LogWarning(ex);
item.Logger?.LogWarning(ex);
}
}, Environment.ProcessorCount, token).ConfigureAwait(false);
}

View File

@@ -97,7 +97,6 @@ public partial class DragAndDrop
{
try
{
System.Console.WriteLine("1");
Value = value;
RuleHelpers.Load(_blazorDiagram, Value);
}

View File

@@ -111,7 +111,7 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
private static async Task Start(RulesLog rulesLog, BlazorDiagram item, CancellationToken cancellationToken)
{
var startNodes = item.Nodes.Where(a => a is StartNode);
startNodes.ForEach(a => (a as INode).LogMessage = rulesLog.Log);
startNodes.ForEach(a => (a as INode).Logger = rulesLog.Log);
foreach (var link in startNodes.SelectMany(a => a.PortLinks))
{
rulesLog.Log.Trace("Start");
@@ -121,7 +121,7 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
private static async Task Analysis(NodeModel targetNode, NodeInput input, ILog log, CancellationToken cancellationToken)
{
(targetNode as INode).LogMessage = log;
(targetNode as INode).Logger = log;
try
{
if (targetNode == null)

View File

@@ -14,7 +14,7 @@ namespace ThingsGateway.Plugin.DB;
public abstract class DynamicSQLBase
{
public TouchSocket.Core.ILog LogMessage { get; set; }
public TouchSocket.Core.ILog Logger { get; set; }
/// <summary>
/// 建库建表

View File

@@ -153,8 +153,8 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
//必须为间隔上传
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
hisModel.LogMessage = LogMessage;
DynamicSQLBase? hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
hisModel.Logger = LogMessage;
await hisModel.DBInit(db, cancellationToken).ConfigureAwait(false);
}

View File

@@ -65,7 +65,7 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
getDeviceModel.LogMessage = LogMessage;
getDeviceModel.Logger = LogMessage;
await getDeviceModel.DBInsertable(db, dbInserts, cancellationToken).ConfigureAwait(false);

View File

@@ -75,7 +75,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
{
var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
getDeviceModel.LogMessage = LogMessage;
getDeviceModel.Logger = LogMessage;
await getDeviceModel.DBInsertable(db, dbInserts, cancellationToken).ConfigureAwait(false);
@@ -112,7 +112,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
if (!_driverPropertys.BigTextScriptRealTable.IsNullOrEmpty())
{
var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptRealTable);
getDeviceModel.LogMessage = LogMessage;
getDeviceModel.Logger = LogMessage;
await getDeviceModel.DBInsertable(db, datas, cancellationToken).ConfigureAwait(false);
return OperResult.Success;

View File

@@ -72,7 +72,7 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableM
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
getDeviceModel.LogMessage = LogMessage;
getDeviceModel.Logger = LogMessage;
await getDeviceModel.DBInsertable(db, dbInserts, cancellationToken).ConfigureAwait(false);
}

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>10.2.3</Version>
<Version>10.2.4</Version>
</PropertyGroup>
<ItemGroup>