Files
KinginfoGateway/src/Gateway/ThingsGateway.RulesEngine/Services/RulesEngineHostedService.cs
2025-04-15 20:59:01 +08:00

302 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人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服务
}