302 lines
10 KiB
C#
302 lines
10 KiB
C#
//------------------------------------------------------------------------------
|
||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||
// 使用文档:https://thingsgateway.cn/
|
||
// QQ群:605534569
|
||
//------------------------------------------------------------------------------
|
||
|
||
using Microsoft.Extensions.Hosting;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
using ThingsGateway.Foundation;
|
||
using ThingsGateway.Gateway.Application;
|
||
using ThingsGateway.NewLife;
|
||
|
||
using TouchSocket.Core;
|
||
|
||
namespace ThingsGateway.RulesEngine;
|
||
public class RulesLog
|
||
{
|
||
public RulesLog(Rules rules, TextFileLogger log)
|
||
{
|
||
Log = log;
|
||
Rules = rules;
|
||
}
|
||
|
||
public TextFileLogger Log { get; set; }
|
||
public Rules Rules { get; set; }
|
||
}
|
||
|
||
internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngineHostedService
|
||
{
|
||
internal const string LogPathFormat = "Logs/RulesEngineLog/{0}";
|
||
internal const string LogDir = "Logs/RulesEngineLog";
|
||
private readonly ILogger _logger;
|
||
/// <inheritdoc cref="RulesEngineHostedService"/>
|
||
public RulesEngineHostedService(ILogger<RulesEngineHostedService> logger, IStringLocalizer<RulesEngineHostedService> localizer)
|
||
{
|
||
_logger = logger;
|
||
Localizer = localizer;
|
||
}
|
||
|
||
private IStringLocalizer Localizer { get; }
|
||
|
||
/// <summary>
|
||
/// 重启锁
|
||
/// </summary>
|
||
private WaitLock RestartLock { get; } = new();
|
||
private List<Rules> Rules { get; set; } = new();
|
||
public Dictionary<RulesLog, BlazorDiagram> BlazorDiagrams { get; private set; } = new();
|
||
|
||
public async Task Edit(Rules rules)
|
||
{
|
||
try
|
||
{
|
||
await Delete(new List<long>() { rules.Id }).ConfigureAwait(false);
|
||
if (rules.Status)
|
||
{
|
||
var data = Init(rules);
|
||
await Start(data.rulesLog, data.blazorDiagram, TokenSource.Token).ConfigureAwait(false);
|
||
var service = App.GetService<IDispatchService<Rules>>();
|
||
service.Dispatch(new());
|
||
}
|
||
|
||
}
|
||
finally
|
||
{
|
||
}
|
||
}
|
||
|
||
public async Task Delete(IEnumerable<long> ids)
|
||
{
|
||
try
|
||
{
|
||
await RestartLock.WaitAsync().ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||
|
||
var dels = BlazorDiagrams.Where(a => ids.Contains(a.Key.Rules.Id)).ToArray();
|
||
foreach (var del in dels)
|
||
{
|
||
if (del.Value != null)
|
||
{
|
||
foreach (var nodeModel in del.Value.Nodes)
|
||
{
|
||
nodeModel.TryDispose();
|
||
}
|
||
del.Value.TryDispose();
|
||
BlazorDiagrams.Remove(del.Key);
|
||
}
|
||
}
|
||
var service = App.GetService<IDispatchService<Rules>>();
|
||
service.Dispatch(new());
|
||
}
|
||
finally
|
||
{
|
||
RestartLock.Release(); // 释放锁
|
||
}
|
||
}
|
||
|
||
private (RulesLog rulesLog, BlazorDiagram blazorDiagram) Init(Rules rules)
|
||
{
|
||
#pragma warning disable CA1863
|
||
var log = TextFileLogger.GetMultipleFileLogger(string.Format(LogPathFormat, rules.Name));
|
||
#pragma warning restore CA1863
|
||
log.LogLevel = TouchSocket.Core.LogLevel.Trace;
|
||
BlazorDiagram blazorDiagram = new();
|
||
RuleHelpers.Load(blazorDiagram, rules.RulesJson);
|
||
var result = (new RulesLog(rules, log), blazorDiagram);
|
||
BlazorDiagrams.Add(result.Item1, blazorDiagram);
|
||
|
||
return result;
|
||
}
|
||
private static Task Start(RulesLog rulesLog, BlazorDiagram item, CancellationToken cancellationToken)
|
||
{
|
||
rulesLog.Log.Trace("Start");
|
||
var startNodes = item.Nodes.Where(a => a is StartNode);
|
||
foreach (var link in startNodes.SelectMany(a => a.PortLinks))
|
||
{
|
||
_ = Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput(), rulesLog, cancellationToken);
|
||
}
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
private static async Task Analysis(NodeModel targetNode, NodeInput input, RulesLog rulesLog, CancellationToken cancellationToken)
|
||
{
|
||
if (targetNode is INode node)
|
||
{
|
||
node.Logger = rulesLog.Log;
|
||
node.RulesEngineName = rulesLog.Rules.Name;
|
||
}
|
||
|
||
try
|
||
{
|
||
if (targetNode == null)
|
||
return;
|
||
if (targetNode is IConditionNode conditionNode)
|
||
{
|
||
var next = await conditionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
||
if (next)
|
||
{
|
||
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
||
{
|
||
await Analysis((link.Target.Model as PortModel)?.Parent, input, rulesLog, cancellationToken).ConfigureAwait(false);
|
||
}
|
||
}
|
||
}
|
||
else if (targetNode is IExpressionNode expressionNode)
|
||
{
|
||
var nodeOutput = await expressionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
||
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
||
{
|
||
await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Value, }, rulesLog, cancellationToken).ConfigureAwait(false);
|
||
}
|
||
}
|
||
else if (targetNode is IActuatorNode actuatorNode)
|
||
{
|
||
var nodeOutput = await actuatorNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
||
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
||
{
|
||
await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Value }, rulesLog, cancellationToken).ConfigureAwait(false);
|
||
}
|
||
}
|
||
else if (targetNode is ITriggerNode triggerNode)
|
||
{
|
||
|
||
Func<NodeOutput, Task> func = (async a =>
|
||
{
|
||
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
||
{
|
||
await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = a.Value }, rulesLog, cancellationToken).ConfigureAwait(false);
|
||
}
|
||
});
|
||
await triggerNode.StartAsync(func).ConfigureAwait(false);
|
||
|
||
}
|
||
|
||
}
|
||
catch (TaskCanceledException) { }
|
||
catch (OperationCanceledException) { }
|
||
catch (Exception ex)
|
||
{
|
||
rulesLog.Log?.LogWarning(ex);
|
||
}
|
||
}
|
||
|
||
|
||
#region worker服务
|
||
|
||
private async Task StartAll(CancellationToken cancellationToken)
|
||
{
|
||
Clear();
|
||
|
||
Rules = await App.GetService<IRulesService>().GetAllAsync().ConfigureAwait(false);
|
||
BlazorDiagrams = new();
|
||
foreach (var rules in Rules.Where(a => a.Status))
|
||
{
|
||
var item = Init(rules);
|
||
await Start(item.rulesLog, item.blazorDiagram, cancellationToken).ConfigureAwait(false);
|
||
}
|
||
var service = App.GetService<IDispatchService<Rules>>();
|
||
service.Dispatch(new());
|
||
|
||
_ = Task.Factory.StartNew(async () =>
|
||
{
|
||
while (!cancellationToken.IsCancellationRequested)
|
||
{
|
||
foreach (var item in BlazorDiagrams?.Values?.SelectMany(a => a.Nodes) ?? new List<NodeModel>())
|
||
{
|
||
if (item is IExexcuteExpressionsBase)
|
||
{
|
||
CSharpScriptEngineExtension.SetExpire((item as TextNode).Text);
|
||
}
|
||
}
|
||
await Task.Delay(60000, cancellationToken).ConfigureAwait(false);
|
||
}
|
||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
|
||
|
||
}
|
||
|
||
|
||
private CancellationTokenSource? TokenSource { get; set; }
|
||
|
||
|
||
|
||
internal async Task StartAsync()
|
||
{
|
||
try
|
||
{
|
||
await RestartLock.WaitAsync().ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||
TokenSource ??= new CancellationTokenSource();
|
||
await StartAll(TokenSource.Token).ConfigureAwait(false);
|
||
_logger.LogInformation(Localizer["RulesEngineTaskStart"]);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Start"); // 记录错误日志
|
||
}
|
||
finally
|
||
{
|
||
RestartLock.Release(); // 释放锁
|
||
}
|
||
}
|
||
|
||
internal async Task StopAsync()
|
||
{
|
||
try
|
||
{
|
||
await RestartLock.WaitAsync().ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||
Cancel();
|
||
Clear();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Stop"); // 记录错误日志
|
||
}
|
||
finally
|
||
{
|
||
RestartLock.Release(); // 释放锁
|
||
}
|
||
}
|
||
|
||
private void Cancel()
|
||
{
|
||
if (TokenSource != null)
|
||
{
|
||
TokenSource.Cancel();
|
||
TokenSource.Dispose();
|
||
TokenSource = null;
|
||
}
|
||
}
|
||
|
||
private void Clear()
|
||
{
|
||
foreach (var item in BlazorDiagrams.Values)
|
||
{
|
||
foreach (var nodeModel in item.Nodes)
|
||
{
|
||
nodeModel.TryDispose();
|
||
}
|
||
}
|
||
BlazorDiagrams.Clear();
|
||
}
|
||
|
||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||
{
|
||
return Task.CompletedTask;
|
||
}
|
||
public override Task StartAsync(CancellationToken cancellationToken)
|
||
{
|
||
return StartAsync();
|
||
}
|
||
|
||
public override Task StopAsync(CancellationToken cancellationToken)
|
||
{
|
||
return StopAsync();
|
||
}
|
||
|
||
|
||
#endregion worker服务
|
||
}
|