refactor:调整部分插件父类
This commit is contained in:
@@ -68,7 +68,6 @@ public sealed class DataValidationAttribute : ValidationAttribute
|
||||
// 进行多语言处理
|
||||
var errorMessage = !string.IsNullOrWhiteSpace(ErrorMessage) ? ErrorMessage : resultMessage;
|
||||
|
||||
//TODO: 修改为类型本地化
|
||||
return new ValidationResult(string.Format(App.StringLocalizerFactory == null ? errorMessage : App.CreateLocalizerByType(validationContext.ObjectType)[errorMessage], validationContext.DisplayName ?? validationContext.MemberName));
|
||||
}
|
||||
|
||||
|
@@ -52,6 +52,29 @@ public static class DictionaryExtensions
|
||||
// 返回成功移除的项目数量
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的一组 key,批量从字典中筛选对应的键值对。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典键类型</typeparam>
|
||||
/// <typeparam name="TValue">字典值类型</typeparam>
|
||||
/// <param name="dictionary">源字典</param>
|
||||
/// <param name="keys">要筛选的 key 集合</param>
|
||||
/// <returns>匹配到的键值对序列</returns>
|
||||
public static IEnumerable<KeyValuePair<TKey, TValue>> FilterByKeys<TKey, TValue>(
|
||||
this IDictionary<TKey, TValue> dictionary,
|
||||
IEnumerable<TKey> keys)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
yield return new KeyValuePair<TKey, TValue>(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量出队
|
||||
/// </summary>
|
||||
@@ -79,6 +102,33 @@ public static class DictionaryExtensions
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量出队
|
||||
/// </summary>
|
||||
public static IEnumerable<T> ToIEnumerableWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0)
|
||||
{
|
||||
if (values.IsEmpty) yield break;
|
||||
if (maxCount <= 0)
|
||||
{
|
||||
maxCount = values.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxCount = Math.Min(maxCount, values.Count);
|
||||
}
|
||||
|
||||
var keys = values.Keys;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (maxCount-- <= 0) break;
|
||||
if (values.TryRemove(key, out var result))
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量出队
|
||||
/// </summary>
|
||||
@@ -108,5 +158,31 @@ public static class DictionaryExtensions
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
/// <summary>
|
||||
/// 批量出队
|
||||
/// </summary>
|
||||
public static IEnumerable<KeyValuePair<TKEY, T>> ToIEnumerableKVWithDequeue<TKEY, T>(this ConcurrentDictionary<TKEY, T> values, int maxCount = 0)
|
||||
{
|
||||
if (values.IsEmpty) yield break;
|
||||
|
||||
if (maxCount <= 0)
|
||||
{
|
||||
maxCount = values.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxCount = Math.Min(maxCount, values.Count);
|
||||
}
|
||||
|
||||
var keys = values.Keys;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (maxCount-- <= 0) break;
|
||||
if (values.TryRemove(key, out var result))
|
||||
{
|
||||
yield return new(key, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -15,6 +15,30 @@ namespace ThingsGateway.Extension.Generic;
|
||||
/// <inheritdoc/>
|
||||
public static class LinqExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将序列分批,每批固定数量
|
||||
/// </summary>
|
||||
public static IEnumerable<List<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
|
||||
{
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
if (batchSize <= 0) throw new ArgumentOutOfRangeException(nameof(batchSize));
|
||||
|
||||
List<T> batch = new List<T>(batchSize);
|
||||
foreach (var item in source)
|
||||
{
|
||||
batch.Add(item);
|
||||
if (batch.Count >= batchSize)
|
||||
{
|
||||
yield return batch;
|
||||
batch = new List<T>(batchSize);
|
||||
}
|
||||
}
|
||||
|
||||
// 剩余不足 batchSize 的最后一批
|
||||
if (batch.Count > 0)
|
||||
yield return batch;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static ICollection<T> AddIF<T>(this ICollection<T> thisValue, bool isOk, Func<T> predicate)
|
||||
{
|
||||
|
@@ -275,9 +275,11 @@ public class TimerX : ITimer, ITimerx, IDisposable
|
||||
//Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now));
|
||||
}
|
||||
|
||||
public bool Disposed { get; private set; }
|
||||
/// <summary>销毁定时器</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
Dispose(true);
|
||||
|
||||
// 告诉GC,不要调用析构函数
|
||||
|
@@ -131,7 +131,7 @@ namespace ThingsGateway.SqlSugar
|
||||
//TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter
|
||||
string key = string.Empty;
|
||||
foreach (var prop in properties)
|
||||
key += prop.Key + ";" + prop.Value.Name + ";";
|
||||
key += $"{prop.Key};{prop.Value.Name};";
|
||||
|
||||
return key;
|
||||
}
|
||||
|
@@ -114,7 +114,7 @@ public static class GenericExtensions
|
||||
public static IEnumerable<IEnumerable<T>> ChunkBetter<T>(this IEnumerable<T> source, int chunkSize, bool isToList = false)
|
||||
{
|
||||
if (chunkSize <= 0)
|
||||
chunkSize = source.Count();
|
||||
yield break;
|
||||
var pos = 0;
|
||||
while (source.Skip(pos).Any())
|
||||
{
|
||||
|
@@ -18,6 +18,7 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
||||
private volatile int _isRunning = 0;
|
||||
private volatile int _pendingTriggers = 0;
|
||||
public Int32 Period => _timer?.Period ?? 0;
|
||||
public bool Enable => _timer?.Disposed != false ? false : true;
|
||||
|
||||
public CronScheduledTask(string interval, Func<object?, CancellationToken, Task> taskFunc, object? state, ILog log, CancellationToken token)
|
||||
{
|
||||
|
@@ -1,146 +1,146 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
////------------------------------------------------------------------------------
|
||||
//// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
//// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
//// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
//// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
//// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
//// 使用文档:https://thingsgateway.cn/
|
||||
//// QQ群:605534569
|
||||
////------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
//using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
//using ThingsGateway.NewLife;
|
||||
|
||||
using TouchSocket.Core;
|
||||
//using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
//namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||
public class DoTask
|
||||
{
|
||||
/// <summary>
|
||||
/// 取消令牌
|
||||
/// </summary>
|
||||
private CancellationTokenSource? _cancelTokenSource;
|
||||
private object? _state;
|
||||
//[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||
//public class DoTask
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// 取消令牌
|
||||
// /// </summary>
|
||||
// private CancellationTokenSource? _cancelTokenSource;
|
||||
// private object? _state;
|
||||
|
||||
public DoTask(Func<object?, CancellationToken, Task> doWork, ILog logger, object? state = null, string taskName = null)
|
||||
{
|
||||
DoWork = doWork; Logger = logger; TaskName = taskName; _state = state;
|
||||
}
|
||||
// public DoTask(Func<object?, CancellationToken, Task> doWork, ILog logger, object? state = null, string taskName = null)
|
||||
// {
|
||||
// DoWork = doWork; Logger = logger; TaskName = taskName; _state = state;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 执行任务方法
|
||||
/// </summary>
|
||||
public Func<object?, CancellationToken, Task> DoWork { get; }
|
||||
private ILog Logger { get; }
|
||||
private Task PrivateTask { get; set; }
|
||||
private string TaskName { get; }
|
||||
// /// <summary>
|
||||
// /// 执行任务方法
|
||||
// /// </summary>
|
||||
// public Func<object?, CancellationToken, Task> DoWork { get; }
|
||||
// private ILog Logger { get; }
|
||||
// private Task PrivateTask { get; set; }
|
||||
// private string TaskName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">调度取消令牌</param>
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
WaitLock.Wait(cancellationToken);
|
||||
// /// <summary>
|
||||
// /// 开始
|
||||
// /// </summary>
|
||||
// /// <param name="cancellationToken">调度取消令牌</param>
|
||||
// public void Start(CancellationToken cancellationToken)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// WaitLock.Wait(cancellationToken);
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
// if (cancellationToken.CanBeCanceled)
|
||||
// {
|
||||
// _cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _cancelTokenSource = new CancellationTokenSource();
|
||||
// }
|
||||
|
||||
// 异步执行
|
||||
PrivateTask = Do();
|
||||
}
|
||||
finally
|
||||
{
|
||||
WaitLock.Release();
|
||||
}
|
||||
}
|
||||
// // 异步执行
|
||||
// PrivateTask = Do();
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// WaitLock.Release();
|
||||
// }
|
||||
// }
|
||||
|
||||
private async Task Do()
|
||||
{
|
||||
await Task.Yield();
|
||||
while (!_cancelTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_cancelTokenSource.IsCancellationRequested)
|
||||
return;
|
||||
await DoWork(_state, _cancelTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex, "DoWork");
|
||||
}
|
||||
}
|
||||
}
|
||||
// private async Task Do()
|
||||
// {
|
||||
// await Task.Yield();
|
||||
// while (!_cancelTokenSource.IsCancellationRequested)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// if (_cancelTokenSource.IsCancellationRequested)
|
||||
// return;
|
||||
// await DoWork(_state, _cancelTokenSource.Token).ConfigureAwait(false);
|
||||
// }
|
||||
// catch (OperationCanceledException)
|
||||
// {
|
||||
// }
|
||||
// catch (ObjectDisposedException)
|
||||
// {
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logger?.LogWarning(ex, "DoWork");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private WaitLock WaitLock = new();
|
||||
/// <summary>
|
||||
/// 停止操作
|
||||
/// </summary>
|
||||
public async Task StopAsync(TimeSpan? waitTime = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await WaitLock.WaitAsync().ConfigureAwait(false);
|
||||
// private WaitLock WaitLock = new();
|
||||
// /// <summary>
|
||||
// /// 停止操作
|
||||
// /// </summary>
|
||||
// public async Task StopAsync(TimeSpan? waitTime = null)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// await WaitLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
_cancelTokenSource?.Cancel();
|
||||
_cancelTokenSource?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex, "Cancel error");
|
||||
}
|
||||
// try
|
||||
// {
|
||||
// _cancelTokenSource?.Cancel();
|
||||
// _cancelTokenSource?.Dispose();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logger?.LogWarning(ex, "Cancel error");
|
||||
// }
|
||||
|
||||
if (PrivateTask != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TaskName != null)
|
||||
Logger?.LogInformation($"{TaskName} Stoping");
|
||||
if (waitTime != null)
|
||||
await PrivateTask.WaitAsync(waitTime.Value).ConfigureAwait(false);
|
||||
if (TaskName != null)
|
||||
Logger?.LogInformation($"{TaskName} Stoped");
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
if (TaskName != null)
|
||||
Logger?.LogWarning($"{TaskName} Stop timeout, exiting wait block");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (TaskName != null)
|
||||
Logger?.LogWarning(ex, $"{TaskName} Stop error");
|
||||
}
|
||||
PrivateTask = null;
|
||||
// if (PrivateTask != null)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// if (TaskName != null)
|
||||
// Logger?.LogInformation($"{TaskName} Stoping");
|
||||
// if (waitTime != null)
|
||||
// await PrivateTask.WaitAsync(waitTime.Value).ConfigureAwait(false);
|
||||
// if (TaskName != null)
|
||||
// Logger?.LogInformation($"{TaskName} Stoped");
|
||||
// }
|
||||
// catch (ObjectDisposedException)
|
||||
// {
|
||||
// }
|
||||
// catch (TimeoutException)
|
||||
// {
|
||||
// if (TaskName != null)
|
||||
// Logger?.LogWarning($"{TaskName} Stop timeout, exiting wait block");
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// if (TaskName != null)
|
||||
// Logger?.LogWarning(ex, $"{TaskName} Stop error");
|
||||
// }
|
||||
// PrivateTask = null;
|
||||
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
WaitLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// WaitLock.Release();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
void Start();
|
||||
void Stop();
|
||||
public Int32 Period { get; }
|
||||
bool Enable { get; }
|
||||
}
|
||||
|
||||
|
||||
|
@@ -17,7 +17,7 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
|
||||
private volatile int _isRunning = 0;
|
||||
private volatile int _pendingTriggers = 0;
|
||||
public Int32 Period => _timer?.Period ?? 0;
|
||||
|
||||
public bool Enable => _timer?.Disposed != false ? false : true;
|
||||
public ScheduledAsyncTask(int interval, Func<object?, CancellationToken, Task> taskFunc, object? state, ILog log, CancellationToken token)
|
||||
{
|
||||
IntervalMS = interval;
|
||||
|
@@ -17,6 +17,7 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter
|
||||
private volatile int _isRunning = 0;
|
||||
private volatile int _pendingTriggers = 0;
|
||||
public Int32 Period => _timer?.Period ?? 0;
|
||||
public bool Enable => _timer?.Disposed != false ? false : true;
|
||||
|
||||
public ScheduledSyncTask(int interval, Action<object?, CancellationToken> taskFunc, object? state, ILog log, CancellationToken token)
|
||||
{
|
||||
|
@@ -13,21 +13,21 @@ namespace ThingsGateway.Gateway.Application;
|
||||
public class SmartTriggerScheduler
|
||||
{
|
||||
private readonly object _lock = new(); // 锁对象,保证线程安全
|
||||
private readonly Func<Task> _action; // 实际要执行的操作
|
||||
private readonly Func<CancellationToken, Task> _action; // 实际要执行的操作
|
||||
private readonly TimeSpan _delay; // 执行间隔(冷却时间)
|
||||
|
||||
private bool _isRunning = false; // 当前是否有调度任务在运行
|
||||
private bool _hasPending = false; // 在等待期间是否有新的触发
|
||||
|
||||
// 构造函数,传入要执行的方法和最小执行间隔
|
||||
public SmartTriggerScheduler(Func<Task> action, TimeSpan minimumInterval)
|
||||
public SmartTriggerScheduler(Func<CancellationToken, Task> action, TimeSpan minimumInterval)
|
||||
{
|
||||
_action = action ?? throw new ArgumentNullException(nameof(action));
|
||||
_delay = minimumInterval;
|
||||
}
|
||||
|
||||
// 外部调用的触发方法(高频调用的地方调用这个)
|
||||
public void Trigger()
|
||||
public void Trigger(CancellationToken cancellationToken = default)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -41,16 +41,16 @@ public class SmartTriggerScheduler
|
||||
|
||||
// 否则启动执行任务
|
||||
_isRunning = true;
|
||||
_ = Task.Run(ExecuteLoop);
|
||||
_ = Task.Run(() => ExecuteLoop(cancellationToken), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
// 实际执行动作的循环逻辑
|
||||
private async Task ExecuteLoop()
|
||||
private async Task ExecuteLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Func<Task> actionToRun = null;
|
||||
Func<CancellationToken, Task> actionToRun = null;
|
||||
|
||||
// 拷贝 _action,并清除等待标记
|
||||
lock (_lock)
|
||||
@@ -60,10 +60,10 @@ public class SmartTriggerScheduler
|
||||
}
|
||||
|
||||
// 执行外部提供的方法
|
||||
await actionToRun().ConfigureAwait(false);
|
||||
await actionToRun(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 等待 delay 时间,进入冷却期
|
||||
await Task.Delay(_delay).ConfigureAwait(false);
|
||||
await Task.Delay(_delay, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
|
@@ -102,7 +102,7 @@ public class ControlController : ControllerBase
|
||||
[DisplayName("重启全部线程")]
|
||||
public async Task RestartAllThread()
|
||||
{
|
||||
await GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.Channels.Values).ConfigureAwait(false);
|
||||
await GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.IdChannels.Values).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -181,7 +181,7 @@ public class ControlController : ControllerBase
|
||||
[DisplayName("删除通道")]
|
||||
public Task<bool> DeleteChannelAsync([FromBody] List<long> ids, bool restart)
|
||||
{
|
||||
if (ids == null || ids.Count == 0) ids = GlobalData.Channels.Keys.ToList();
|
||||
if (ids == null || ids.Count == 0) ids = GlobalData.IdChannels.Keys.ToList();
|
||||
return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart, default);
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 业务插件
|
||||
/// </summary>
|
||||
public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache
|
||||
{
|
||||
|
||||
protected override bool AlarmModelEnable => true;
|
||||
|
||||
protected override bool DevModelEnable => false;
|
||||
|
||||
protected override bool VarModelEnable => false;
|
||||
|
||||
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceBasicData>> item, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableBasicData>> item, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override ValueTask<OperResult> UpdateVarModels(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.ReadOnlyIdVariables.Where(a => a.Value.AlarmEnable));
|
||||
|
||||
|
||||
var ids = IdVariableRuntimes.Select(b => b.Value.DeviceId).ToHashSet();
|
||||
|
||||
CollectDevices = GlobalData.ReadOnlyIdDevices
|
||||
.Where(a => IdVariableRuntimes.Select(b => b.Value.DeviceId).Contains(a.Value.Id))
|
||||
.ToDictionary(a => a.Key, a => a.Value);
|
||||
}
|
||||
protected internal override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
GlobalData.AlarmChangedEvent -= AlarmValueChange;
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
|
||||
{
|
||||
AlarmValueChange(a.Value);
|
||||
});
|
||||
GlobalData.AlarmChangedEvent += AlarmValueChange;
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
GlobalData.AlarmChangedEvent -= AlarmValueChange;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
/// <summary>
|
||||
/// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="alarmVariable">报警变量</param>
|
||||
protected void AlarmValueChange(AlarmVariable alarmVariable)
|
||||
{
|
||||
if (CurrentDevice?.Pause != false)
|
||||
return;
|
||||
|
||||
if (AlarmModelEnable) return;
|
||||
// 如果业务属性的缓存为间隔上传,则不执行后续操作
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
// 检查当前设备的变量是否包含此报警变量,如果包含,则触发报警变量的变化处理方法
|
||||
if (IdVariableRuntimes.ContainsKey(alarmVariable.Id))
|
||||
AlarmChange(alarmVariable);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueAlarmModel"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="alarmVariable">报警变量</param>
|
||||
protected virtual void AlarmChange(AlarmVariable alarmVariable)
|
||||
{
|
||||
// 在报警状态变化时执行的自定义逻辑
|
||||
}
|
||||
|
||||
public override void PauseThread(bool pause)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var oldV = CurrentDevice.Pause;
|
||||
base.PauseThread(pause);
|
||||
if (!pause && oldV != pause)
|
||||
{
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
|
||||
{
|
||||
AlarmChange(a.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,263 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 业务插件,实现实体VarModel,AlarmModel缓存
|
||||
/// </summary>
|
||||
public abstract class BusinessBaseWithCacheAlarmModel<VarModel, DevModel, AlarmModel> : BusinessBaseWithCacheDeviceModel<VarModel, DevModel>
|
||||
{
|
||||
protected ConcurrentQueue<CacheDBItem<AlarmModel>> _memoryAlarmModelQueue = new();
|
||||
|
||||
private volatile bool LocalDBCacheAlarmModelInited;
|
||||
private CacheDB DBCacheAlarm;
|
||||
protected internal override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
DBCacheAlarm = LocalDBCacheAlarmModel();
|
||||
return base.InitChannelAsync(channel, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddCache(List<CacheDBItem<AlarmModel>> data)
|
||||
{
|
||||
|
||||
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogMessage?.LogInformation($"Add {typeof(DevModel).Name} data to file cache, count {data.Count}");
|
||||
foreach (var item in data)
|
||||
{
|
||||
item.Id = CommonUtils.GetSingleId();
|
||||
}
|
||||
var dir = CacheDBUtil.GetCacheFilePath(CurrentDevice.Name.ToString());
|
||||
var fileStart = CacheDBUtil.GetFileName($"{CurrentDevice.PluginName}_{typeof(AlarmModel).FullName}_{nameof(AlarmModel)}");
|
||||
var fullName = dir.CombinePathWithOs($"{fileStart}{CacheDBUtil.EX}");
|
||||
|
||||
lock (fullName)
|
||||
{
|
||||
bool s = false;
|
||||
while (!s)
|
||||
{
|
||||
s = CacheDBUtil.DeleteCache(_businessPropertyWithCache.CacheFileMaxLength, fullName);
|
||||
}
|
||||
using var cache = LocalDBCacheAlarmModel();
|
||||
cache.DBProvider.Fastest<CacheDBItem<AlarmModel>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
using var cache = LocalDBCacheAlarmModel();
|
||||
lock (cache.CacheDBOption.FileFullName)
|
||||
{
|
||||
cache.DBProvider.Fastest<CacheDBItem<AlarmModel>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Add cache fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加队列,超限后会入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddQueueAlarmModel(CacheDBItem<AlarmModel> data)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
//检测队列长度,超限存入缓存数据库
|
||||
if (_memoryAlarmModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
List<CacheDBItem<AlarmModel>> list = null;
|
||||
lock (_memoryAlarmModelQueue)
|
||||
{
|
||||
if (_memoryAlarmModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
list = _memoryAlarmModelQueue.ToListWithDequeue();
|
||||
}
|
||||
}
|
||||
AddCache(list);
|
||||
}
|
||||
}
|
||||
if (_memoryAlarmModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
lock (_memoryAlarmModelQueue)
|
||||
{
|
||||
if (_memoryAlarmModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
LogMessage?.LogWarning($"{typeof(AlarmModel).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase [QueueMaxCount] or Enable cache");
|
||||
_memoryAlarmModelQueue.Clear();
|
||||
_memoryAlarmModelQueue.Enqueue(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryAlarmModelQueue.Enqueue(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存对象,注意每次获取的对象可能不一样,如顺序操作,需固定引用
|
||||
/// </summary>
|
||||
protected virtual CacheDB LocalDBCacheAlarmModel()
|
||||
{
|
||||
var cacheDb = CacheDBUtil.GetCache(typeof(CacheDBItem<AlarmModel>), CurrentDevice.Name.ToString(), $"{CurrentDevice.PluginName}_{typeof(AlarmModel).Name}");
|
||||
|
||||
if (!LocalDBCacheAlarmModelInited)
|
||||
{
|
||||
cacheDb.InitDb();
|
||||
LocalDBCacheAlarmModelInited = true;
|
||||
}
|
||||
return cacheDb;
|
||||
}
|
||||
|
||||
protected override async Task Update(CancellationToken cancellationToken)
|
||||
{
|
||||
await UpdateVarModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateDevModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateAlarmModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateDevModelCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateAlarmModelCache(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 需实现上传到通道
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected abstract ValueTask<OperResult> UpdateAlarmModel(IEnumerable<CacheDBItem<AlarmModel>> item, CancellationToken cancellationToken);
|
||||
|
||||
protected async Task UpdateAlarmModelCache(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
#region //成功上传时,补上传缓存数据
|
||||
|
||||
if (IsConnected())
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
//循环获取,固定读最大行数量,执行完成需删除行
|
||||
var varList = await DBCacheAlarm.DBProvider.Queryable<CacheDBItem<AlarmModel>>().Take(_businessPropertyWithCache.SplitSize).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (varList.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdateAlarmModel(varList, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
//删除缓存
|
||||
await DBCacheAlarm.DBProvider.Deleteable<CacheDBItem<AlarmModel>>(varList).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion //成功上传时,补上传缓存数据
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task UpdateAlarmModelMemory(CancellationToken cancellationToken)
|
||||
{
|
||||
#region //上传设备内存队列中的数据
|
||||
|
||||
try
|
||||
{
|
||||
var list = _memoryAlarmModelQueue.ToListWithDequeue();
|
||||
if (list?.Count > 0)
|
||||
{
|
||||
var data = list.ChunkBetter(_businessPropertyWithCache.SplitSize);
|
||||
foreach (var item in data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdateAlarmModel(item, cancellationToken).ConfigureAwait(false);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
AddCache(item.ToList());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
|
||||
#endregion //上传设备内存队列中的数据
|
||||
}
|
||||
}
|
@@ -1,264 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 业务插件,实现实体VarModel,DevModel缓存
|
||||
/// </summary>
|
||||
public abstract class BusinessBaseWithCacheDeviceModel<VarModel, DevModel> : BusinessBaseWithCacheVariableModel<VarModel>
|
||||
{
|
||||
protected ConcurrentQueue<CacheDBItem<DevModel>> _memoryDevModelQueue = new();
|
||||
|
||||
private volatile bool LocalDBCacheDevModelInited;
|
||||
|
||||
private CacheDB DBCacheDev;
|
||||
protected internal override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
DBCacheDev = LocalDBCacheDevModel();
|
||||
return base.InitChannelAsync(channel, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddCache(List<CacheDBItem<DevModel>> data)
|
||||
{
|
||||
|
||||
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
LogMessage?.LogInformation($"Add {typeof(DevModel).Name} data to file cache, count {data.Count}");
|
||||
foreach (var item in data)
|
||||
{
|
||||
item.Id = CommonUtils.GetSingleId();
|
||||
}
|
||||
var dir = CacheDBUtil.GetCacheFilePath(CurrentDevice.Name.ToString());
|
||||
var fileStart = CacheDBUtil.GetFileName($"{CurrentDevice.PluginName}_{typeof(DevModel).FullName}_{nameof(DevModel)}");
|
||||
var fullName = dir.CombinePathWithOs($"{fileStart}{CacheDBUtil.EX}");
|
||||
|
||||
lock (fullName)
|
||||
{
|
||||
bool s = false;
|
||||
while (!s)
|
||||
{
|
||||
s = CacheDBUtil.DeleteCache(_businessPropertyWithCache.CacheFileMaxLength, fullName);
|
||||
}
|
||||
using var cache = LocalDBCacheDevModel();
|
||||
cache.DBProvider.Fastest<CacheDBItem<DevModel>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
using var cache = LocalDBCacheDevModel();
|
||||
lock (cache.CacheDBOption.FileFullName)
|
||||
{
|
||||
cache.DBProvider.Fastest<CacheDBItem<DevModel>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Add cache fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加队列,超限后会入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddQueueDevModel(CacheDBItem<DevModel> data)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
//检测队列长度,超限存入缓存数据库
|
||||
if (_memoryDevModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
List<CacheDBItem<DevModel>> list = null;
|
||||
lock (_memoryDevModelQueue)
|
||||
{
|
||||
if (_memoryDevModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
list = _memoryDevModelQueue.ToListWithDequeue();
|
||||
}
|
||||
}
|
||||
AddCache(list);
|
||||
}
|
||||
}
|
||||
if (_memoryDevModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
lock (_memoryDevModelQueue)
|
||||
{
|
||||
if (_memoryDevModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
LogMessage?.LogWarning($"{typeof(DevModel).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase [QueueMaxCount] or Enable cache");
|
||||
_memoryDevModelQueue.Clear();
|
||||
_memoryDevModelQueue.Enqueue(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryDevModelQueue.Enqueue(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存对象,注意每次获取的对象可能不一样,如顺序操作,需固定引用
|
||||
/// </summary>
|
||||
protected virtual CacheDB LocalDBCacheDevModel()
|
||||
{
|
||||
var cacheDb = CacheDBUtil.GetCache(typeof(CacheDBItem<DevModel>), CurrentDevice.Name.ToString(), $"{CurrentDevice.PluginName}_{typeof(DevModel).Name}");
|
||||
if (!LocalDBCacheDevModelInited)
|
||||
{
|
||||
cacheDb.InitDb();
|
||||
LocalDBCacheDevModelInited = true;
|
||||
}
|
||||
return cacheDb;
|
||||
}
|
||||
|
||||
protected override async Task Update(CancellationToken cancellationToken)
|
||||
{
|
||||
await UpdateVarModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateDevModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateDevModelCache(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 需实现上传到通道
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected abstract ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DevModel>> item, CancellationToken cancellationToken);
|
||||
|
||||
protected async Task UpdateDevModelCache(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
#region //成功上传时,补上传缓存数据
|
||||
|
||||
if (IsConnected())
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
||||
//循环获取
|
||||
var varList = await DBCacheDev.DBProvider.Queryable<CacheDBItem<DevModel>>().Take(_businessPropertyWithCache.SplitSize).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (varList.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdateDevModel(varList, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
//删除缓存
|
||||
await DBCacheDev.DBProvider.Deleteable<CacheDBItem<DevModel>>(varList).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion //成功上传时,补上传缓存数据
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task UpdateDevModelMemory(CancellationToken cancellationToken)
|
||||
{
|
||||
#region //上传设备内存队列中的数据
|
||||
|
||||
try
|
||||
{
|
||||
var list = _memoryDevModelQueue.ToListWithDequeue();
|
||||
if (list?.Count > 0)
|
||||
{
|
||||
var data = list.ChunkBetter(_businessPropertyWithCache.SplitSize);
|
||||
foreach (var item in data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdateDevModel(item, cancellationToken).ConfigureAwait(false);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
AddCache(item.ToList());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
|
||||
#endregion //上传设备内存队列中的数据
|
||||
}
|
||||
}
|
@@ -17,77 +17,101 @@ namespace ThingsGateway.Gateway.Application;
|
||||
/// <summary>
|
||||
/// 业务插件,额外实现变量、设备、变量间隔上传
|
||||
/// </summary>
|
||||
public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel, AlarmModel> : BusinessBaseWithCacheAlarmModel<VarModel, DevModel, AlarmModel>
|
||||
public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 业务属性
|
||||
/// 获取具体业务属性的缓存设置。
|
||||
/// </summary>
|
||||
protected sealed override BusinessPropertyWithCache _businessPropertyWithCache => _businessPropertyWithCacheInterval;
|
||||
|
||||
/// <summary>
|
||||
/// 业务属性
|
||||
/// 获取具体业务属性的缓存间隔设置。
|
||||
/// </summary>
|
||||
protected abstract BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval { get; }
|
||||
|
||||
|
||||
|
||||
protected internal override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
GlobalData.AlarmChangedEvent -= AlarmValueChange;
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
|
||||
if (AlarmModelEnable)
|
||||
{
|
||||
AlarmValueChange(a.Value);
|
||||
});
|
||||
GlobalData.AlarmChangedEvent -= AlarmValueChange;
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
|
||||
{
|
||||
AlarmValueChange(a.Value);
|
||||
});
|
||||
|
||||
GlobalData.AlarmChangedEvent += AlarmValueChange;
|
||||
// 解绑全局数据的事件
|
||||
|
||||
|
||||
// 根据业务属性的缓存是否为间隔上传来决定事件绑定
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
GlobalData.AlarmChangedEvent += AlarmValueChange;
|
||||
// 解绑全局数据的事件
|
||||
}
|
||||
if (DevModelEnable)
|
||||
{
|
||||
// 绑定全局数据的事件
|
||||
GlobalData.DeviceStatusChangeEvent += DeviceStatusChange;
|
||||
GlobalData.VariableValueChangeEvent += VariableValueChange;
|
||||
|
||||
// 如果不是间隔上传,则订阅全局变量值改变事件和设备状态改变事件,并触发一次事件处理
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
{
|
||||
GlobalData.DeviceStatusChangeEvent += DeviceStatusChange;
|
||||
}
|
||||
}
|
||||
|
||||
if (VarModelEnable)
|
||||
{
|
||||
// 注册变量值变化事件处理程序
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
{
|
||||
GlobalData.VariableValueChangeEvent += VariableValueChange;
|
||||
}
|
||||
}
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
|
||||
if (_businessPropertyWithCacheInterval.IsAllVariable)
|
||||
if (AlarmModelEnable || DevModelEnable || VarModelEnable)
|
||||
{
|
||||
LogMessage?.LogInformation("Refresh variable");
|
||||
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
|
||||
if (_businessPropertyWithCacheInterval.IsAllVariable)
|
||||
{
|
||||
LogMessage?.LogInformation("Refresh variable");
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 触发一次设备状态变化和变量值变化事件
|
||||
CollectDevices?.ForEach(a =>
|
||||
if (DevModelEnable)
|
||||
{
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
DeviceStatusChange(a.Value, a.Value.AdaptDeviceBasicData());
|
||||
});
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
|
||||
CollectDevices?.ForEach(a =>
|
||||
{
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
DeviceStatusChange(a.Value, a.Value.AdaptDeviceBasicData());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (VarModelEnable)
|
||||
{
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
});
|
||||
// 触发一次变量值变化事件
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCacheAlarmModel{T,T2,T3}.AddQueueAlarmModel(CacheDBItem{T3})"/> 方法。
|
||||
/// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueAlarmModel"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="alarmVariable">报警变量</param>
|
||||
protected virtual void AlarmChange(AlarmVariable alarmVariable)
|
||||
@@ -96,7 +120,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当设备状态变化时触发此方法。如果不需要进行设备上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCacheDeviceModel{T,T2}.AddQueueDevModel(CacheDBItem{T2})"/> 方法。
|
||||
/// 当设备状态变化时触发此方法。如果不需要进行设备上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueDevModel"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="deviceRuntime">设备运行时信息</param>
|
||||
/// <param name="deviceData">设备数据</param>
|
||||
@@ -105,7 +129,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
// 在设备状态变化时执行的自定义逻辑
|
||||
}
|
||||
/// <summary>
|
||||
/// 当设备状态定时变化时触发此方法。如果不需要进行设备上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCacheDeviceModel{T,T2}.AddQueueDevModel(CacheDBItem{T2})"/> 方法。
|
||||
/// 当设备状态定时变化时触发此方法。如果不需要进行设备上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueDevModel"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="deviceRuntime">设备运行时信息</param>
|
||||
/// <param name="deviceData">设备数据</param>
|
||||
@@ -113,6 +137,9 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
{
|
||||
// 在设备状态变化时执行的自定义逻辑
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源方法
|
||||
/// </summary>
|
||||
@@ -137,7 +164,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
/// </summary>
|
||||
protected void IntervalInsert(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
if (CurrentDevice.Pause == true)
|
||||
if (CurrentDevice?.Pause != false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -145,35 +172,42 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
// 如果业务属性的缓存为间隔上传,则根据定时器间隔执行相应操作
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Change)
|
||||
{
|
||||
try
|
||||
|
||||
if (VarModelEnable)
|
||||
{
|
||||
if (LogMessage?.LogLevel <= LogLevel.Debug)
|
||||
LogMessage?.LogDebug($"Interval {typeof(VarModel).Name} data, count {IdVariableRuntimes.Count}");
|
||||
// 间隔推送全部变量
|
||||
var variableRuntimes = IdVariableRuntimes.Select(a => a.Value);
|
||||
VariableTimeInterval(variableRuntimes, variableRuntimes.AdaptIEnumerableVariableBasicData());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, AppResource.IntervalInsertVariableFail);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (CollectDevices != null)
|
||||
try
|
||||
{
|
||||
if (LogMessage?.LogLevel <= LogLevel.Debug)
|
||||
LogMessage?.LogDebug($"Interval {typeof(DevModel).Name} data, count {CollectDevices.Count}");
|
||||
|
||||
// 间隔推送全部设备
|
||||
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))
|
||||
{
|
||||
DeviceTimeInterval(deviceRuntime, deviceRuntime.AdaptDeviceBasicData());
|
||||
}
|
||||
LogMessage?.LogDebug($"Interval {typeof(VariableBasicData).Name} data, count {IdVariableRuntimes.Count}");
|
||||
// 间隔推送全部变量
|
||||
var variableRuntimes = IdVariableRuntimes.Select(a => a.Value);
|
||||
VariableTimeInterval(variableRuntimes, variableRuntimes.AdaptIEnumerableVariableBasicData());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, AppResource.IntervalInsertVariableFail);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (DevModelEnable)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, AppResource.IntervalInsertDeviceFail);
|
||||
try
|
||||
{
|
||||
if (CollectDevices != null)
|
||||
{
|
||||
if (LogMessage?.LogLevel <= LogLevel.Debug)
|
||||
LogMessage?.LogDebug($"Interval {typeof(DeviceBasicData).Name} data, count {CollectDevices.Count}");
|
||||
|
||||
// 间隔推送全部设备
|
||||
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))
|
||||
{
|
||||
DeviceTimeInterval(deviceRuntime, deviceRuntime.AdaptDeviceBasicData());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, AppResource.IntervalInsertDeviceFail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +221,7 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当变量状态变化时触发此方法。如果不需要进行变量上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCacheVariableModel{T}.AddQueueVarModel(CacheDBItem{T})"/> 方法。
|
||||
/// 当变量状态变化时触发此方法。如果不需要进行变量上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueVarModel(CacheDBItem{VariableBasicData})"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntime">变量运行时信息</param>
|
||||
/// <param name="variable">变量数据</param>
|
||||
@@ -195,8 +229,9 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
{
|
||||
// 在变量状态变化时执行的自定义逻辑
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当变量定时变化时触发此方法。如果不需要进行变量上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCacheVariableModel{T}.AddQueueVarModel(CacheDBItem{T})"/> 方法。
|
||||
/// 当变量定时变化时触发此方法。如果不需要进行变量上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueVarModel(CacheDBItem{VariableBasicData})"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntimes">变量运行时信息</param>
|
||||
/// <param name="variables">变量数据</param>
|
||||
@@ -204,14 +239,17 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
{
|
||||
// 在变量状态变化时执行的自定义逻辑
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="alarmVariable">报警变量</param>
|
||||
private void AlarmValueChange(AlarmVariable alarmVariable)
|
||||
protected void AlarmValueChange(AlarmVariable alarmVariable)
|
||||
{
|
||||
if (CurrentDevice.Pause)
|
||||
if (CurrentDevice?.Pause != false)
|
||||
return;
|
||||
|
||||
if (!AlarmModelEnable) return;
|
||||
// 如果业务属性的缓存为间隔上传,则不执行后续操作
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
@@ -220,6 +258,8 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
AlarmChange(alarmVariable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void PauseThread(bool pause)
|
||||
{
|
||||
lock (this)
|
||||
@@ -228,20 +268,29 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
base.PauseThread(pause);
|
||||
if (!pause && oldV != pause)
|
||||
{
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
|
||||
if (AlarmModelEnable)
|
||||
{
|
||||
AlarmChange(a.Value);
|
||||
});
|
||||
CollectDevices?.ForEach(a =>
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
|
||||
{
|
||||
AlarmChange(a.Value);
|
||||
});
|
||||
}
|
||||
if (DevModelEnable)
|
||||
{
|
||||
CollectDevices?.ForEach(a =>
|
||||
{
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
DeviceStatusChange(a.Value, a.Value.AdaptDeviceBasicData());
|
||||
});
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
}
|
||||
if (VarModelEnable)
|
||||
{
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,10 +300,11 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
/// </summary>
|
||||
/// <param name="deviceRuntime">设备运行时信息</param>
|
||||
/// <param name="deviceData">设备数据</param>
|
||||
private void DeviceStatusChange(DeviceRuntime deviceRuntime, DeviceBasicData deviceData)
|
||||
protected void DeviceStatusChange(DeviceRuntime deviceRuntime, DeviceBasicData deviceData)
|
||||
{
|
||||
if (CurrentDevice.Pause == true)
|
||||
if (CurrentDevice?.Pause != false)
|
||||
return;
|
||||
if (!DevModelEnable) return;
|
||||
// 如果业务属性的缓存为间隔上传,则不执行后续操作
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
@@ -269,10 +319,11 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
/// </summary>
|
||||
/// <param name="variableRuntime">变量运行时信息</param>
|
||||
/// <param name="variable">变量数据</param>
|
||||
private void VariableValueChange(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
protected void VariableValueChange(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
{
|
||||
if (CurrentDevice.Pause == true)
|
||||
if (CurrentDevice?.Pause != false)
|
||||
return;
|
||||
if (!VarModelEnable) return;
|
||||
// 如果业务属性的缓存为间隔上传,则不执行后续操作
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
@@ -281,4 +332,6 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
VariableChange(variableRuntime, variable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -21,7 +21,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
/// <summary>
|
||||
/// 业务插件,额外实现脚本切换实体
|
||||
/// </summary>
|
||||
public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevModel, AlarmModel> : BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel, AlarmModel>
|
||||
public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBaseWithCacheInterval
|
||||
{
|
||||
protected sealed override BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval => _businessPropertyWithCacheIntervalScript;
|
||||
|
||||
@@ -30,7 +30,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
public virtual List<string> Match(string input)
|
||||
{
|
||||
// 生成缓存键,以确保缓存的唯一性
|
||||
var cacheKey = $"{nameof(BusinessBaseWithCacheIntervalScript<VarModel, DevModel, AlarmModel>)}-{CultureInfo.CurrentUICulture.Name}-Match-{input}";
|
||||
var cacheKey = $"{nameof(BusinessBaseWithCacheIntervalScript)}-{CultureInfo.CurrentUICulture.Name}-Match-{input}";
|
||||
|
||||
// 尝试从缓存中获取匹配结果,如果缓存中不存在则创建新的匹配结果
|
||||
var strings = App.CacheService.GetOrAdd(cacheKey, entry =>
|
||||
@@ -61,10 +61,9 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
|
||||
#region 封装方法
|
||||
|
||||
protected List<TopicJson> GetAlarms(IEnumerable<AlarmModel> item)
|
||||
protected IEnumerable<TopicJson> GetAlarms(IEnumerable<AlarmVariable> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmVariable>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -92,7 +91,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
var gList = group.Select(a => a).ToList();
|
||||
string json = gList.ToSystemTextJsonString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json, gList.Count));
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -101,7 +100,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
string json = SystemTextJsonExtension.ToSystemTextJsonString(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json, 1));
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,26 +112,24 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var gList = data.Select(a => a).ToList();
|
||||
string json = gList.ToSystemTextJsonString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
string json = SystemTextJsonExtension.ToSystemTextJsonString(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return topicJsonList;
|
||||
}
|
||||
|
||||
|
||||
protected List<TopicJson> GetDeviceData(IEnumerable<DevModel> item)
|
||||
protected IEnumerable<TopicJson> GetDeviceData(IEnumerable<DeviceBasicData> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<DeviceBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -160,7 +157,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
var gList = group.Select(a => a).ToList();
|
||||
string json = gList.ToSystemTextJsonString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json, gList.Count));
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -169,7 +166,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
string json = SystemTextJsonExtension.ToSystemTextJsonString(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json, 1));
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,24 +179,22 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var gList = data.Select(a => a).ToList();
|
||||
string json = gList.ToSystemTextJsonString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
string json = SystemTextJsonExtension.ToSystemTextJsonString(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return topicJsonList;
|
||||
}
|
||||
|
||||
protected List<TopicJson> GetVariable(IEnumerable<VarModel> item)
|
||||
protected IEnumerable<TopicJson> GetVariableScript(IEnumerable<VariableBasicData> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<VariableBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -227,7 +222,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
var gList = group.Select(a => a).ToList();
|
||||
string json = gList.ToSystemTextJsonString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json, gList.Count));
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -236,7 +231,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
string json = SystemTextJsonExtension.ToSystemTextJsonString(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json, 1));
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,33 +244,33 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var gList = data.Select(a => a).ToList();
|
||||
string json = gList.ToSystemTextJsonString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
string json = SystemTextJsonExtension.ToSystemTextJsonString(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return topicJsonList;
|
||||
}
|
||||
|
||||
|
||||
protected List<TopicJson> GetVariableBasicData(IEnumerable<VariableBasicData> item)
|
||||
protected IEnumerable<TopicJson> GetVariableBasicData(IEnumerable<VariableBasicData> item)
|
||||
{
|
||||
IEnumerable<VariableBasicData>? data = null;
|
||||
if (!_businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel.IsNullOrWhiteSpace())
|
||||
{
|
||||
return GetVariable(item.Cast<VarModel>());
|
||||
foreach (var v in GetVariableScript(item))
|
||||
yield return v;
|
||||
|
||||
yield break; // 提前结束
|
||||
}
|
||||
else
|
||||
{
|
||||
data = item;
|
||||
}
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -303,7 +298,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
// 如果是变量列表,则将整个分组转换为 JSON 字符串
|
||||
string json = group.Select(a => a).GroupBy(a => a.DeviceName, b => b).ToDictionary(a => a.Key, b => b.ToList()).ToSystemTextJsonString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json, group.Count()));
|
||||
yield return new(topic, json, group.Count());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -312,7 +307,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
string json = SystemTextJsonExtension.ToSystemTextJsonString(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json, 1));
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,41 +319,22 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
|
||||
{
|
||||
string json = data.Select(a => a).GroupBy(a => a.DeviceName, b => b).ToDictionary(a => a.Key, b => b.ToList()).ToSystemTextJsonString(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, data.Count()));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, data.Count());
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
string json = SystemTextJsonExtension.ToSystemTextJsonString(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return topicJsonList;
|
||||
}
|
||||
|
||||
//protected static byte[] Serialize(object value)
|
||||
//{
|
||||
// var block = new ValueByteBlock(1024 * 64);
|
||||
// try
|
||||
// {
|
||||
// //将数据序列化到内存块
|
||||
// FastBinaryFormatter.Serialize(ref block, value);
|
||||
// block.SeekToStart();
|
||||
// return block.Memory.GetArray().Array;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// block.Dispose();
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
protected List<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmModel> item)
|
||||
protected IEnumerable<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmVariable> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
List<TopicArray> topicArrayList = new List<TopicArray>();
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmVariable>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -384,7 +360,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
var gList = group.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicArrayList.Add(new(topic, json, gList.Count));
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -393,7 +369,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicArrayList.Add(new(topic, json, 1));
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,25 +381,23 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var gList = data.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return topicArrayList;
|
||||
}
|
||||
|
||||
protected List<TopicArray> GetDeviceTopicArray(IEnumerable<DevModel> item)
|
||||
protected IEnumerable<TopicArray> GetDeviceTopicArray(IEnumerable<DeviceBasicData> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
List<TopicArray> topicArrayList = new List<TopicArray>();
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<DeviceBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -451,7 +425,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
var gList = group.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicArrayList.Add(new(topic, json, gList.Count));
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -460,7 +434,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicArrayList.Add(new(topic, json, 1));
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -473,24 +447,22 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var gList = data.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return topicArrayList;
|
||||
}
|
||||
|
||||
protected List<TopicArray> GetVariableTopicArray(IEnumerable<VarModel> item)
|
||||
protected IEnumerable<TopicArray> GetVariableScriptTopicArray(IEnumerable<VariableBasicData> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
List<TopicArray> topicArrayList = new List<TopicArray>();
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<VariableBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -518,7 +490,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
var gList = group.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicArrayList.Add(new(topic, json, gList.Count));
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -527,7 +499,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicArrayList.Add(new(topic, json, 1));
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -540,33 +512,34 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var gList = data.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return topicArrayList;
|
||||
}
|
||||
|
||||
protected List<TopicArray> GetVariableBasicDataTopicArray(IEnumerable<VariableBasicData> item)
|
||||
protected IEnumerable<TopicArray> GetVariableBasicDataTopicArray(IEnumerable<VariableBasicData> item)
|
||||
{
|
||||
IEnumerable<VariableBasicData>? data = null;
|
||||
if (!_businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel.IsNullOrWhiteSpace())
|
||||
{
|
||||
return GetVariableTopicArray(item.Cast<VarModel>());
|
||||
foreach (var v in GetVariableScriptTopicArray(item))
|
||||
yield return v;
|
||||
|
||||
yield break; // 提前结束
|
||||
}
|
||||
else
|
||||
{
|
||||
data = item;
|
||||
}
|
||||
|
||||
List<TopicArray> topicArrayList = new List<TopicArray>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -594,7 +567,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
var gList = group.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicArrayList.Add(new(topic, json, gList.Count));
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -603,7 +576,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicArrayList.Add(new(topic, json, 1));
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -616,18 +589,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
var gList = data.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicArrayList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1));
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return topicArrayList;
|
||||
}
|
||||
|
||||
|
@@ -8,12 +8,16 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// 业务插件,额外实现脚本切换实体
|
||||
/// </summary>
|
||||
public class SynchronizationVariableProperty : VariablePropertyBase
|
||||
public abstract partial class BusinessBaseWithCacheIntervalScriptAll : BusinessBaseWithCacheIntervalScript
|
||||
{
|
||||
public override bool Enable { get; set; } = true;
|
||||
}
|
||||
protected override bool AlarmModelEnable => true;
|
||||
|
||||
protected override bool DevModelEnable => true;
|
||||
|
||||
protected override bool VarModelEnable => true;
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 业务插件
|
||||
/// </summary>
|
||||
public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCacheInterval
|
||||
{
|
||||
|
||||
protected override bool AlarmModelEnable => false;
|
||||
|
||||
protected override bool DevModelEnable => false;
|
||||
|
||||
protected override bool VarModelEnable => true;
|
||||
|
||||
protected override ValueTask<OperResult> UpdateDevModel(IEnumerable<CacheDBItem<DeviceBasicData>> item, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
protected override ValueTask<OperResult> UpdateAlarmModel(IEnumerable<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -1,483 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 业务插件,实现实体VarModel缓存
|
||||
/// </summary>
|
||||
public abstract class BusinessBaseWithCacheVariableModel<VarModel> : BusinessBase
|
||||
{
|
||||
protected ConcurrentQueue<CacheDBItem<VarModel>> _memoryVarModelQueue = new();
|
||||
protected ConcurrentQueue<CacheDBItem<List<VarModel>>> _memoryVarModelsQueue = new();
|
||||
protected volatile bool success = true;
|
||||
private volatile bool LocalDBCacheVarModelInited;
|
||||
private volatile bool LocalDBCacheVarModelsInited;
|
||||
private CacheDB DBCacheVar;
|
||||
private CacheDB DBCacheVars;
|
||||
protected internal override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
DBCacheVar = LocalDBCacheVarModel();
|
||||
DBCacheVars = LocalDBCacheVarModels();
|
||||
return base.InitChannelAsync(channel, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
protected sealed override BusinessPropertyBase _businessPropertyBase => _businessPropertyWithCache;
|
||||
|
||||
protected abstract BusinessPropertyWithCache _businessPropertyWithCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddCache(List<CacheDBItem<VarModel>> data)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogMessage?.LogInformation($"Add {typeof(VarModel).Name} data to file cache, count {data.Count}");
|
||||
foreach (var item in data)
|
||||
{
|
||||
item.Id = CommonUtils.GetSingleId();
|
||||
}
|
||||
var dir = CacheDBUtil.GetCacheFilePath(CurrentDevice.Name.ToString());
|
||||
var fileStart = CacheDBUtil.GetFileName($"{CurrentDevice.PluginName}_{typeof(VarModel).Name}");
|
||||
var fullName = dir.CombinePathWithOs($"{fileStart}{CacheDBUtil.EX}");
|
||||
|
||||
lock (this)
|
||||
{
|
||||
bool s = false;
|
||||
while (!s)
|
||||
{
|
||||
s = CacheDBUtil.DeleteCache(_businessPropertyWithCache.CacheFileMaxLength, fullName);
|
||||
}
|
||||
LocalDBCacheVarModelInited = false;
|
||||
using var cache = LocalDBCacheVarModel();
|
||||
cache.DBProvider.Fastest<CacheDBItem<VarModel>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
using var cache = LocalDBCacheVarModel();
|
||||
lock (cache.CacheDBOption.FileFullName)
|
||||
cache.DBProvider.Fastest<CacheDBItem<VarModel>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Add cache fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddCache(List<CacheDBItem<List<VarModel>>> data)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var item in data)
|
||||
{
|
||||
item.Id = CommonUtils.GetSingleId();
|
||||
}
|
||||
var dir = CacheDBUtil.GetCacheFilePath(CurrentDevice.Name.ToString());
|
||||
var fileStart = CacheDBUtil.GetFileName($"{CurrentDevice.PluginName}_List_{typeof(VarModel).Name}");
|
||||
var fullName = dir.CombinePathWithOs($"{fileStart}{CacheDBUtil.EX}");
|
||||
|
||||
lock (this)
|
||||
{
|
||||
bool s = false;
|
||||
while (!s)
|
||||
{
|
||||
s = CacheDBUtil.DeleteCache(_businessPropertyWithCache.CacheFileMaxLength, fullName);
|
||||
}
|
||||
LocalDBCacheVarModelsInited = false;
|
||||
using var cache = LocalDBCacheVarModels();
|
||||
cache.DBProvider.Fastest<CacheDBItem<List<VarModel>>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
using var cache = LocalDBCacheVarModels();
|
||||
lock (cache.CacheDBOption.FileFullName)
|
||||
cache.DBProvider.Fastest<CacheDBItem<List<VarModel>>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Add cache fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加队列,超限后会入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddQueueVarModel(CacheDBItem<VarModel> data)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
//检测队列长度,超限存入缓存数据库
|
||||
if (_memoryVarModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
List<CacheDBItem<VarModel>> list = null;
|
||||
lock (_memoryVarModelQueue)
|
||||
{
|
||||
if (_memoryVarModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
list = _memoryVarModelQueue.ToListWithDequeue();
|
||||
}
|
||||
}
|
||||
AddCache(list);
|
||||
}
|
||||
}
|
||||
if (_memoryVarModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
lock (_memoryVarModelQueue)
|
||||
{
|
||||
if (_memoryVarModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
LogMessage?.LogWarning($"{typeof(VarModel).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase [QueueMaxCount] or Enable cache");
|
||||
_memoryVarModelQueue.Clear();
|
||||
_memoryVarModelQueue.Enqueue(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryVarModelQueue.Enqueue(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加队列,超限后会入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddQueueVarModel(CacheDBItem<List<VarModel>> data)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
//检测队列长度,超限存入缓存数据库
|
||||
if (_memoryVarModelsQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
List<CacheDBItem<List<VarModel>>> list = null;
|
||||
lock (_memoryVarModelsQueue)
|
||||
{
|
||||
if (_memoryVarModelsQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
list = _memoryVarModelsQueue.ToListWithDequeue();
|
||||
}
|
||||
}
|
||||
AddCache(list);
|
||||
}
|
||||
}
|
||||
if (_memoryVarModelsQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
lock (_memoryVarModelsQueue)
|
||||
{
|
||||
if (_memoryVarModelsQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
_memoryVarModelsQueue.Clear();
|
||||
_memoryVarModelsQueue.Enqueue(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryVarModelsQueue.Enqueue(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存对象,注意using
|
||||
/// </summary>
|
||||
protected virtual CacheDB LocalDBCacheVarModel()
|
||||
{
|
||||
var cacheDb = CacheDBUtil.GetCache(typeof(CacheDBItem<VarModel>), CurrentDevice.Name.ToString(), $"{CurrentDevice.PluginName}_{typeof(VarModel).Name}");
|
||||
if (!LocalDBCacheVarModelInited)
|
||||
{
|
||||
cacheDb.InitDb();
|
||||
LocalDBCacheVarModelInited = true;
|
||||
}
|
||||
return cacheDb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存对象,注意using
|
||||
/// </summary>
|
||||
protected virtual CacheDB LocalDBCacheVarModels()
|
||||
{
|
||||
var cacheDb = CacheDBUtil.GetCache(typeof(CacheDBItem<List<VarModel>>), CurrentDevice.Name.ToString(), $"{CurrentDevice.PluginName}_List_{typeof(VarModel).Name}");
|
||||
if (!LocalDBCacheVarModelsInited)
|
||||
{
|
||||
cacheDb.InitDb();
|
||||
LocalDBCacheVarModelsInited = true;
|
||||
}
|
||||
return cacheDb;
|
||||
}
|
||||
protected virtual async Task Update(CancellationToken cancellationToken)
|
||||
{
|
||||
await UpdateVarModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsCache(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 需实现上传到通道
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected abstract ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VarModel>> item, CancellationToken cancellationToken);
|
||||
/// <summary>
|
||||
/// 需实现上传到通道
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected abstract ValueTask<OperResult> UpdateVarModels(IEnumerable<VarModel> item, CancellationToken cancellationToken);
|
||||
protected async Task UpdateVarModelCache(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
#region //成功上传时,补上传缓存数据
|
||||
|
||||
if (IsConnected())
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
//循环获取
|
||||
|
||||
var varList = await DBCacheVar.DBProvider.Queryable<CacheDBItem<VarModel>>().Take(_businessPropertyWithCache.SplitSize).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (varList.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdateVarModel(varList, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
//删除缓存
|
||||
await DBCacheVar.DBProvider.Deleteable<CacheDBItem<VarModel>>(varList).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion //成功上传时,补上传缓存数据
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task UpdateVarModelsCache(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
#region //成功上传时,补上传缓存数据
|
||||
|
||||
if (IsConnected())
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
//循环获取
|
||||
|
||||
var varList = await DBCacheVars.DBProvider.Queryable<CacheDBItem<List<VarModel>>>().FirstAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (varList?.Value?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdateVarModels(varList.Value, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
//删除缓存
|
||||
await DBCacheVars.DBProvider.Deleteable<CacheDBItem<List<VarModel>>>(varList).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion //成功上传时,补上传缓存数据
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task UpdateVarModelMemory(CancellationToken cancellationToken)
|
||||
{
|
||||
#region //上传变量内存队列中的数据
|
||||
|
||||
try
|
||||
{
|
||||
var list = _memoryVarModelQueue.ToListWithDequeue();
|
||||
if (list?.Count > 0)
|
||||
{
|
||||
var data = list.ChunkBetter(_businessPropertyWithCache.SplitSize);
|
||||
foreach (var item in data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdateVarModel(item, cancellationToken).ConfigureAwait(false);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
AddCache(item.ToList());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
|
||||
#endregion //上传变量内存队列中的数据
|
||||
}
|
||||
|
||||
protected async Task UpdateVarModelsMemory(CancellationToken cancellationToken)
|
||||
{
|
||||
#region //上传变量内存队列中的数据
|
||||
|
||||
try
|
||||
{
|
||||
while (_memoryVarModelsQueue.TryDequeue(out var cacheDBItem))
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
var list = cacheDBItem.Value;
|
||||
var data = list.ChunkBetter(_businessPropertyWithCache.SplitSize);
|
||||
foreach (var item in data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdateVarModels(item, cancellationToken).ConfigureAwait(false);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
|
||||
AddCache(new List<CacheDBItem<List<VarModel>>>() { new CacheDBItem<List<VarModel>>(item.ToList()) });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
|
||||
#endregion //上传变量内存队列中的数据
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -1,246 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 业务插件的抽象基类,用于实现设备和变量之间的间隔上传功能。
|
||||
/// </summary>
|
||||
/// <typeparam name="VarModel">变量数据类型</typeparam>
|
||||
/// <typeparam name="DevModel">设备数据类型</typeparam>
|
||||
public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevModel> : BusinessBaseWithCacheDeviceModel<VarModel, DevModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取具体业务属性的缓存设置。
|
||||
/// </summary>
|
||||
protected sealed override BusinessPropertyWithCache _businessPropertyWithCache => _businessPropertyWithCacheInterval;
|
||||
|
||||
/// <summary>
|
||||
/// 获取业务属性与缓存间隔的抽象属性。
|
||||
/// </summary>
|
||||
protected abstract BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval { get; }
|
||||
|
||||
|
||||
|
||||
protected internal override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
// 如果不是间隔上传,则订阅全局变量值改变事件和设备状态改变事件,并触发一次事件处理
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
{
|
||||
GlobalData.DeviceStatusChangeEvent += DeviceStatusChange;
|
||||
GlobalData.VariableValueChangeEvent += VariableValueChange;
|
||||
|
||||
}
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
|
||||
if (_businessPropertyWithCacheInterval.IsAllVariable)
|
||||
{
|
||||
LogMessage?.LogInformation("Refresh variable");
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
CollectDevices?.ForEach(a =>
|
||||
{
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
DeviceStatusChange(a.Value, a.Value.AdaptDeviceBasicData());
|
||||
});
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设备状态变化时发生的虚拟方法,用于处理设备状态变化事件。
|
||||
/// </summary>
|
||||
/// <param name="deviceRuntime">设备运行时对象</param>
|
||||
/// <param name="deviceData">设备数据对象</param>
|
||||
protected virtual void DeviceChange(DeviceRuntime deviceRuntime, DeviceBasicData deviceData)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// 当设备状态定时变化时触发此方法。如果不需要进行设备上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCacheDeviceModel{T,T2}.AddQueueDevModel(CacheDBItem{T2})"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="deviceRuntime">设备运行时信息</param>
|
||||
/// <param name="deviceData">设备数据</param>
|
||||
protected virtual void DeviceTimeInterval(DeviceRuntime deviceRuntime, DeviceBasicData deviceData)
|
||||
{
|
||||
// 在设备状态变化时执行的自定义逻辑
|
||||
}
|
||||
/// <summary>
|
||||
/// 释放资源的方法,释放插件相关资源。
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否释放托管资源</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
// 注销全局变量值改变事件和设备状态改变事件的订阅
|
||||
GlobalData.VariableValueChangeEvent -= VariableValueChange;
|
||||
GlobalData.DeviceStatusChangeEvent -= DeviceStatusChange;
|
||||
|
||||
// 清空内存队列
|
||||
_memoryDevModelQueue.Clear();
|
||||
_memoryVarModelQueue.Clear();
|
||||
_memoryVarModelsQueue.Clear();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 间隔上传数据的方法
|
||||
/// </summary>
|
||||
protected void IntervalInsert(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
if (CurrentDevice.Pause == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果业务属性的缓存为间隔上传,则根据定时器间隔执行相应操作
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Change)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LogMessage?.LogLevel <= LogLevel.Debug)
|
||||
LogMessage?.LogDebug($"Interval {typeof(VarModel).Name} data, count {IdVariableRuntimes.Count}");
|
||||
// 上传所有变量信息
|
||||
var variableRuntimes = IdVariableRuntimes.Select(a => a.Value);
|
||||
VariableTimeInterval(variableRuntimes, variableRuntimes.AdaptIEnumerableVariableBasicData());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, AppResource.IntervalInsertVariableFail);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (CollectDevices != null)
|
||||
{
|
||||
if (LogMessage?.LogLevel <= LogLevel.Debug)
|
||||
LogMessage?.LogDebug($"Interval {typeof(DevModel).Name} data, count {CollectDevices.Count}");
|
||||
// 上传所有设备信息
|
||||
foreach (var deviceRuntime in CollectDevices.Select(a => a.Value))
|
||||
{
|
||||
DeviceTimeInterval(deviceRuntime, deviceRuntime.AdaptDeviceBasicData());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, AppResource.IntervalInsertDeviceFail);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = base.ProtectedGetTasks(cancellationToken);
|
||||
list.Add(ScheduledTaskHelper.GetTask(_businessPropertyWithCacheInterval.BusinessInterval, IntervalInsert, null, LogMessage, cancellationToken));
|
||||
return list;
|
||||
}
|
||||
/// <summary>
|
||||
/// 变量状态变化时发生的虚拟方法,用于处理变量状态变化事件。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntime">变量运行时对象</param>
|
||||
/// <param name="variable">变量数据对象</param>
|
||||
protected virtual void VariableChange(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// 当变量定时变化时触发此方法。如果不需要进行变量上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCacheVariableModel{T}.AddQueueVarModel(CacheDBItem{T})"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntimes">变量运行时信息</param>
|
||||
/// <param name="variables">变量数据</param>
|
||||
protected virtual void VariableTimeInterval(IEnumerable<VariableRuntime> variableRuntimes, IEnumerable<VariableBasicData> variables)
|
||||
{
|
||||
// 在变量状态变化时执行的自定义逻辑
|
||||
}
|
||||
/// <summary>
|
||||
/// 设备状态改变时的事件处理方法。
|
||||
/// </summary>
|
||||
/// <param name="deviceRuntime">设备运行时对象</param>
|
||||
/// <param name="deviceData">设备数据对象</param>
|
||||
private void DeviceStatusChange(DeviceRuntime deviceRuntime, DeviceBasicData deviceData)
|
||||
{
|
||||
// 如果当前设备已停止运行,则直接返回,不进行处理
|
||||
if (CurrentDevice.Pause == true)
|
||||
return;
|
||||
|
||||
// 如果业务属性不是间隔上传,则执行设备状态改变的处理逻辑
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
// 检查当前设备集合中是否包含该设备,并进行相应处理
|
||||
if (CollectDevices?.ContainsKey(deviceRuntime.Id) == true)
|
||||
DeviceChange(deviceRuntime, deviceData);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变量值改变时的事件处理方法。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntime">变量运行时对象</param>
|
||||
/// <param name="variable">变量数据对象</param>
|
||||
private void VariableValueChange(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
{
|
||||
// 如果当前设备已停止运行,则直接返回,不进行处理
|
||||
if (CurrentDevice.Pause == true)
|
||||
return;
|
||||
|
||||
// 如果业务属性不是间隔上传,则执行变量状态改变的处理逻辑
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
// 检查当前设备是否包含该变量,并进行相应处理
|
||||
if (IdVariableRuntimes.ContainsKey(variableRuntime.Id))
|
||||
VariableChange(variableRuntime, variable);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PauseThread(bool pause)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var oldV = CurrentDevice.Pause;
|
||||
base.PauseThread(pause);
|
||||
if (!pause && oldV != pause)
|
||||
{
|
||||
CollectDevices?.ForEach(a =>
|
||||
{
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
DeviceStatusChange(a.Value, a.Value.AdaptDeviceBasicData());
|
||||
});
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,169 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象类 <see cref="BusinessBaseWithCacheIntervalVariableModel{VarModel}"/>,表示具有缓存间隔功能的业务基类,其中 T 代表变量模型。
|
||||
/// </summary>
|
||||
/// <typeparam name="VarModel">变量模型类型</typeparam>
|
||||
public abstract class BusinessBaseWithCacheIntervalVariableModel<VarModel> : BusinessBaseWithCacheVariableModel<VarModel>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 获取具体业务属性的缓存设置。
|
||||
/// </summary>
|
||||
protected sealed override BusinessPropertyWithCache _businessPropertyWithCache => _businessPropertyWithCacheInterval;
|
||||
|
||||
/// <summary>
|
||||
/// 获取具体业务属性的缓存间隔设置。
|
||||
/// </summary>
|
||||
protected abstract BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval { get; }
|
||||
|
||||
protected internal override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
// 注册变量值变化事件处理程序
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
{
|
||||
GlobalData.VariableValueChangeEvent += VariableValueChange;
|
||||
}
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
|
||||
if (_businessPropertyWithCacheInterval.IsAllVariable)
|
||||
{
|
||||
LogMessage?.LogInformation("Refresh variable");
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// 触发一次变量值变化事件
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源的方法。
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否释放托管资源</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
GlobalData.VariableValueChangeEvent -= VariableValueChange;
|
||||
_memoryVarModelQueue.Clear();
|
||||
_memoryVarModelsQueue.Clear();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 间隔上传数据的方法
|
||||
/// </summary>
|
||||
protected void IntervalInsert(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
if (CurrentDevice.Pause == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果业务属性的缓存为间隔上传,则根据定时器间隔执行相应操作
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Change)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LogMessage?.LogLevel <= LogLevel.Debug)
|
||||
LogMessage?.LogDebug($"Interval {typeof(VarModel).Name} data, count {IdVariableRuntimes.Count}");
|
||||
// 上传所有变量信息
|
||||
var variableRuntimes = IdVariableRuntimes.Select(a => a.Value);
|
||||
VariableTimeInterval(variableRuntimes, variableRuntimes.AdaptIEnumerableVariableBasicData());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, AppResource.IntervalInsertVariableFail);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = base.ProtectedGetTasks(cancellationToken);
|
||||
list.Add(ScheduledTaskHelper.GetTask(_businessPropertyWithCacheInterval.BusinessInterval, IntervalInsert, null, LogMessage, cancellationToken));
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当变量状态变化时发生,通常需要执行<see cref="BusinessBaseWithCacheVariableModel{T}.AddQueueVarModel(CacheDBItem{T})"/>。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntime">变量运行时对象</param>
|
||||
/// <param name="variable">变量运行时对象</param>
|
||||
protected virtual void VariableChange(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当变量定时变化时触发此方法。如果不需要进行变量上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCacheVariableModel{T}.AddQueueVarModel(CacheDBItem{T})"/> 方法。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntimes">变量运行时信息</param>
|
||||
/// <param name="variables">变量数据</param>
|
||||
protected virtual void VariableTimeInterval(IEnumerable<VariableRuntime> variableRuntimes, IEnumerable<VariableBasicData> variables)
|
||||
{
|
||||
// 在变量状态变化时执行的自定义逻辑
|
||||
}
|
||||
/// <summary>
|
||||
/// 当变量值发生变化时调用的方法。
|
||||
/// </summary>
|
||||
/// <param name="variableRuntime">变量运行时对象</param>
|
||||
/// <param name="variable">变量数据</param>
|
||||
protected void VariableValueChange(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
{
|
||||
if (CurrentDevice.Pause == true)
|
||||
return;
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
//筛选
|
||||
if (IdVariableRuntimes.ContainsKey(variableRuntime.Id))
|
||||
VariableChange(variableRuntime, variable);
|
||||
}
|
||||
}
|
||||
public override void PauseThread(bool pause)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var oldV = CurrentDevice.Pause;
|
||||
base.PauseThread(pause);
|
||||
if (!pause && oldV != pause)
|
||||
{
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -68,7 +68,7 @@ public static class GlobalData
|
||||
public static async Task<IEnumerable<ChannelRuntime>> GetCurrentUserChannels()
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
return ReadOnlyChannels.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
return ReadOnlyIdChannels.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
}
|
||||
public static async Task<IEnumerable<DeviceRuntime>> GetCurrentUserDevices()
|
||||
@@ -126,7 +126,7 @@ public static class GlobalData
|
||||
/// </summary>
|
||||
public static IEnumerable<ChannelRuntime> GetEnableChannels()
|
||||
{
|
||||
return Channels.Where(a => a.Value.Enable).Select(a => a.Value);
|
||||
return IdChannels.Where(a => a.Value.Enable).Select(a => a.Value);
|
||||
}
|
||||
|
||||
public static VariableRuntime GetVariable(string deviceName, string variableName)
|
||||
@@ -414,12 +414,20 @@ public static class GlobalData
|
||||
/// <summary>
|
||||
/// 只读的通道字典,提供对通道的只读访问
|
||||
/// </summary>
|
||||
public static IReadOnlyDictionary<long, ChannelRuntime> ReadOnlyChannels => Channels;
|
||||
public static IReadOnlyDictionary<long, ChannelRuntime> ReadOnlyIdChannels => IdChannels;
|
||||
/// <summary>
|
||||
/// 只读的通道字典,提供对通道的只读访问
|
||||
/// </summary>
|
||||
public static IReadOnlyDictionary<string, ChannelRuntime> ReadOnlyChannels => Channels;
|
||||
|
||||
/// <summary>
|
||||
/// 内部使用的通道字典,用于存储通道对象
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<long, ChannelRuntime> Channels { get; } = new();
|
||||
internal static ConcurrentDictionary<long, ChannelRuntime> IdChannels { get; } = new();
|
||||
/// <summary>
|
||||
/// 内部使用的通道字典,用于存储通道对象
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<string, ChannelRuntime> Channels { get; } = new();
|
||||
|
||||
|
||||
|
||||
|
@@ -16,6 +16,7 @@ public static class AppResource
|
||||
public static string RulesEngineTaskStart => ThingsGateway.Foundation.AppResource.Lang == Language.Chinese ? ChineseResource.RulesEngineTaskStart : EnglishResource.RulesEngineTaskStart;
|
||||
|
||||
public static string RealAlarmTaskStart => ThingsGateway.Foundation.AppResource.Lang == Language.Chinese ? ChineseResource.RealAlarmTaskStart : EnglishResource.RealAlarmTaskStart;
|
||||
public static string RealAlarmTaskStop => ThingsGateway.Foundation.AppResource.Lang == Language.Chinese ? ChineseResource.RealAlarmTaskStop : EnglishResource.RealAlarmTaskStop;
|
||||
public static string IntervalInsertAlarmFail => ThingsGateway.Foundation.AppResource.Lang == Language.Chinese ? ChineseResource.IntervalInsertAlarmFail : EnglishResource.IntervalInsertAlarmFail;
|
||||
public static string IntervalInsertDeviceFail => ThingsGateway.Foundation.AppResource.Lang == Language.Chinese ? ChineseResource.IntervalInsertDeviceFail : EnglishResource.IntervalInsertDeviceFail;
|
||||
public static string IntervalInsertVariableFail => ThingsGateway.Foundation.AppResource.Lang == Language.Chinese ? ChineseResource.IntervalInsertVariableFail : EnglishResource.IntervalInsertVariableFail;
|
||||
@@ -50,6 +51,7 @@ public static class ChineseResource
|
||||
public const string RulesEngineTaskStart = "规则引擎线程启动";
|
||||
|
||||
public const string RealAlarmTaskStart = "实时报警服务启动";
|
||||
public const string RealAlarmTaskStop = "实时报警服务停止";
|
||||
public const string IntervalInsertAlarmFail = "间隔上传报警失败";
|
||||
public const string IntervalInsertDeviceFail = "间隔上传设备失败";
|
||||
public const string IntervalInsertVariableFail = "间隔上传变量失败";
|
||||
@@ -87,6 +89,8 @@ public static class EnglishResource
|
||||
public const string RulesEngineTaskStart = "Rules engine service started";
|
||||
|
||||
public const string RealAlarmTaskStart = "Real-time alarm service started";
|
||||
public const string RealAlarmTaskStop = "Real-time alarm service stoped";
|
||||
|
||||
public const string IntervalInsertAlarmFail = "Failed to upload alarms periodically";
|
||||
public const string IntervalInsertDeviceFail = "Failed to upload device data periodically";
|
||||
public const string IntervalInsertVariableFail = "Failed to upload variable data periodically";
|
||||
|
@@ -228,6 +228,8 @@
|
||||
"Connect": "Connect",
|
||||
"ConnectTimeout": "ConnectTimeout",
|
||||
"CopyChannel": "Copy Channel",
|
||||
"UpdateGatewayData": "UpdateGatewayData",
|
||||
"InsertGatewayData": "InsertGatewayData",
|
||||
"CreateTime": "CreateTime",
|
||||
"CreateUser": "CreateUser",
|
||||
"DataBits": "DataBits",
|
||||
|
@@ -227,6 +227,8 @@
|
||||
"Connect": "连接",
|
||||
"ConnectTimeout": "连接超时",
|
||||
"CopyChannel": "复制通道",
|
||||
"UpdateGatewayData": "更新网关点位配置",
|
||||
"InsertGatewayData": "添加网关点位配置",
|
||||
"CreateTime": "创建时间",
|
||||
"CreateUser": "创建人",
|
||||
"DataBits": "数据位",
|
||||
|
@@ -55,7 +55,6 @@ public static partial class GatewayMapper
|
||||
public static partial Device AdaptDevice(this Device src);
|
||||
public static partial Variable AdaptVariable(this Variable src);
|
||||
public static partial List<PluginInfo> AdaptListPluginInfo(this List<PluginInfo> src);
|
||||
public static partial List<VariableBasicData> AdaptIEnumerableVariableBasicData(this IGrouping<string, VariableBasicData> src);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -120,9 +120,11 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
// 通过插件名称获取插件信息
|
||||
PluginInfo = GlobalData.PluginService.GetList().FirstOrDefault(A => A.FullName == PluginName);
|
||||
|
||||
GlobalData.Channels.TryRemove(Id, out _);
|
||||
GlobalData.IdChannels.TryRemove(Id, out _);
|
||||
GlobalData.Channels.TryRemove(Name, out _);
|
||||
|
||||
GlobalData.Channels.TryAdd(Id, this);
|
||||
GlobalData.IdChannels.TryAdd(Id, this);
|
||||
GlobalData.Channels.TryAdd(Name, this);
|
||||
|
||||
}
|
||||
|
||||
@@ -130,7 +132,8 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
{
|
||||
//Config?.SafeDispose();
|
||||
|
||||
GlobalData.Channels.TryRemove(Id, out _);
|
||||
GlobalData.IdChannels.TryRemove(Id, out _);
|
||||
GlobalData.Channels.TryRemove(Name, out _);
|
||||
DeviceThreadManage = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
@@ -146,7 +149,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
|
||||
public IChannel GetChannel(TouchSocketConfig config)
|
||||
{
|
||||
lock (GlobalData.Channels)
|
||||
lock (GlobalData.IdChannels)
|
||||
{
|
||||
|
||||
if (DeviceThreadManage?.Channel?.DisposedValue == false)
|
||||
@@ -159,7 +162,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
)
|
||||
{
|
||||
//获取相同配置的Tcp服务或Udp服务或COM
|
||||
var same = GlobalData.Channels.FirstOrDefault(a =>
|
||||
var same = GlobalData.IdChannels.FirstOrDefault(a =>
|
||||
{
|
||||
if (a.Value == this)
|
||||
return false;
|
||||
|
@@ -126,11 +126,12 @@ public class VariableBasicData
|
||||
/// <inheritdoc cref="VariableRuntime.IsOnline"/>
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.DeviceRuntime"/>
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
public DeviceBasicData DeviceRuntime { get; set; }
|
||||
|
||||
///// <inheritdoc cref="VariableRuntime.DeviceRuntime"/>
|
||||
//[System.Text.Json.Serialization.JsonIgnore]
|
||||
//[Newtonsoft.Json.JsonIgnore]
|
||||
//public DeviceBasicData DeviceRuntime { get; set; }
|
||||
/// <inheritdoc cref="Variable.DeviceId"/>
|
||||
public long DeviceId { get; set; }
|
||||
/// <inheritdoc cref="VariableRuntime.LastErrorMessage"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
|
@@ -59,6 +59,72 @@ public class ChannelRuntimeService : IChannelRuntimeService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var result = await GlobalData.ChannelService.InsertAsync(models, devices, variables).ConfigureAwait(false);
|
||||
var ids = models.Select(a => a.Id).ToHashSet();
|
||||
|
||||
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(ids).ConfigureAwait(false);
|
||||
|
||||
var deviceids = devices.Select(a => a.Id).ToHashSet();
|
||||
var newDeviceRuntimes = await RuntimeServiceHelper.GetNewDeviceRuntimesAsync(deviceids).ConfigureAwait(false);
|
||||
|
||||
await RuntimeServiceHelper.InitAsync(newChannelRuntimes, newDeviceRuntimes, _logger).ConfigureAwait(false);
|
||||
|
||||
//根据条件重启通道线程
|
||||
if (restart)
|
||||
{
|
||||
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
|
||||
|
||||
await RuntimeServiceHelper.ChangedDriverAsync(_logger, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
WaitLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var result = await GlobalData.ChannelService.UpdateAsync(models, devices, variables).ConfigureAwait(false);
|
||||
var ids = models.Select(a => a.Id).ToHashSet();
|
||||
|
||||
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(ids).ConfigureAwait(false);
|
||||
|
||||
var deviceids = devices.Select(a => a.Id).ToHashSet();
|
||||
var newDeviceRuntimes = await RuntimeServiceHelper.GetNewDeviceRuntimesAsync(deviceids).ConfigureAwait(false);
|
||||
|
||||
await RuntimeServiceHelper.InitAsync(newChannelRuntimes, newDeviceRuntimes, _logger).ConfigureAwait(false);
|
||||
|
||||
//根据条件重启通道线程
|
||||
if (restart)
|
||||
{
|
||||
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
|
||||
|
||||
await RuntimeServiceHelper.ChangedDriverAsync(_logger, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
WaitLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart = true)
|
||||
{
|
||||
try
|
||||
@@ -202,7 +268,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
|
||||
await WaitLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
//网关启动时,获取所有通道
|
||||
var newChannelRuntimes = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id) || !GlobalData.Channels.ContainsKey(a.Id)).AdaptListChannelRuntime();
|
||||
var newChannelRuntimes = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id) || !GlobalData.IdChannels.ContainsKey(a.Id)).AdaptListChannelRuntime();
|
||||
|
||||
var chanelIds = newChannelRuntimes.Select(a => a.Id).ToHashSet();
|
||||
var newDeviceRuntimes = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).Where(a => chanelIds.Contains(a.ChannelId)).AdaptListDeviceRuntime();
|
||||
|
@@ -32,6 +32,81 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
{
|
||||
#region CURD
|
||||
|
||||
/// <inheritdoc/>
|
||||
[OperDesc("InsertGatewayData", localizerType: typeof(Channel), isRecordPar: false)]
|
||||
public async Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables)
|
||||
{
|
||||
using var db = GetDB();
|
||||
|
||||
//事务
|
||||
var result = await db.UseTranAsync(async () =>
|
||||
{
|
||||
ManageHelper.CheckChannelCount(models.Count);
|
||||
|
||||
await db.Insertable(models).ExecuteCommandAsync().ConfigureAwait(false);
|
||||
|
||||
ManageHelper.CheckDeviceCount(devices.Count);
|
||||
|
||||
await db.Insertable(devices).ExecuteCommandAsync().ConfigureAwait(false);
|
||||
|
||||
ManageHelper.CheckVariableCount(variables.Count);
|
||||
|
||||
await db.Insertable(variables).ExecuteCommandAsync().ConfigureAwait(false);
|
||||
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
DeleteChannelFromCache();
|
||||
App.GetService<IDeviceService>().DeleteDeviceFromCache();
|
||||
App.GetService<IVariableService>().DeleteVariableCache();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
throw new(result.ErrorMessage, result.ErrorException);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
[OperDesc("UpdateGatewayData", localizerType: typeof(Channel), isRecordPar: false)]
|
||||
public async Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables)
|
||||
{
|
||||
using var db = GetDB();
|
||||
|
||||
//事务
|
||||
var result = await db.UseTranAsync(async () =>
|
||||
{
|
||||
|
||||
await db.Updateable(models).ExecuteCommandAsync().ConfigureAwait(false);
|
||||
|
||||
|
||||
await db.Updateable(devices).ExecuteCommandAsync().ConfigureAwait(false);
|
||||
|
||||
|
||||
await db.Updateable(variables).ExecuteCommandAsync().ConfigureAwait(false);
|
||||
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
DeleteChannelFromCache();
|
||||
App.GetService<IDeviceService>().DeleteDeviceFromCache();
|
||||
App.GetService<IVariableService>().DeleteVariableCache();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
throw new(result.ErrorMessage, result.ErrorException);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
[OperDesc("CopyChannel", localizerType: typeof(Channel), isRecordPar: false)]
|
||||
public async Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices)
|
||||
|
@@ -57,5 +57,6 @@ public interface IChannelRuntimeService
|
||||
Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> data);
|
||||
Task RestartChannelAsync(IEnumerable<ChannelRuntime> oldChannelRuntimes);
|
||||
Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken);
|
||||
|
||||
Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken);
|
||||
Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken);
|
||||
}
|
@@ -101,5 +101,6 @@ internal interface IChannelService
|
||||
/// 保存是否输出日志和日志等级
|
||||
/// </summary>
|
||||
Task UpdateLogAsync(long channelId, TouchSocket.Core.LogLevel logLevel);
|
||||
|
||||
Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables);
|
||||
Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables);
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
@@ -106,7 +107,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService
|
||||
var result = await GlobalData.DeviceService.DeleteDeviceAsync(devids).ConfigureAwait(false);
|
||||
|
||||
//根据条件重启通道线程
|
||||
var deviceRuntimes = GlobalData.IdDevices.Where(a => devids.Contains(a.Key)).Select(a => a.Value).ToList();
|
||||
var deviceRuntimes = GlobalData.IdDevices.FilterByKeys(devids).Select(a => a.Value).ToList();
|
||||
|
||||
ConcurrentHashSet<IDriver> changedDriver = RuntimeServiceHelper.DeleteDeviceRuntime(deviceRuntimes);
|
||||
|
||||
|
@@ -11,9 +11,6 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
@@ -26,438 +23,21 @@ internal sealed class AlarmHostedService : BackgroundService, IAlarmHostedServic
|
||||
public AlarmHostedService(ILogger<AlarmHostedService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
AlarmTask = new AlarmTask(_logger);
|
||||
}
|
||||
|
||||
#region 核心实现
|
||||
|
||||
/// <summary>
|
||||
/// 获取bool报警类型
|
||||
/// </summary>
|
||||
/// <param name="tag">要检查的变量</param>
|
||||
/// <param name="limit">报警限制值</param>
|
||||
/// <param name="expressions">报警约束表达式</param>
|
||||
/// <param name="text">报警文本</param>
|
||||
/// <returns>报警类型枚举</returns>
|
||||
private static AlarmTypeEnum? GetBoolAlarmCode(VariableRuntime tag, out string limit, out string expressions, out string text)
|
||||
private AlarmTask AlarmTask;
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
limit = string.Empty; // 初始化报警限制值为空字符串
|
||||
expressions = string.Empty; // 初始化报警约束表达式为空字符串
|
||||
text = string.Empty; // 初始化报警文本为空字符串
|
||||
|
||||
if (tag?.Value == null) // 检查变量是否为null或其值为null
|
||||
{
|
||||
return null; // 如果是,则返回null
|
||||
}
|
||||
|
||||
if (tag.BoolCloseAlarmEnable && !tag.Value.ToBoolean(true)) // 检查是否启用了关闭报警功能,并且变量的布尔值为false
|
||||
{
|
||||
limit = false.ToString(); // 将报警限制值设置为"false"
|
||||
expressions = tag.BoolCloseRestrainExpressions!; // 获取关闭报警的约束表达式
|
||||
text = tag.BoolCloseAlarmText!; // 获取关闭报警时的报警文本
|
||||
return AlarmTypeEnum.Close; // 返回关闭报警类型枚举
|
||||
}
|
||||
|
||||
if (tag.BoolOpenAlarmEnable && tag.Value.ToBoolean(false)) // 检查是否启用了开启报警功能,并且变量的布尔值为true
|
||||
{
|
||||
limit = true.ToString(); // 将报警限制值设置为"true"
|
||||
expressions = tag.BoolOpenRestrainExpressions!; // 获取开启报警的约束表达式
|
||||
text = tag.BoolOpenAlarmText!; // 获取开启报警时的报警文本
|
||||
return AlarmTypeEnum.Open; // 返回开启报警类型枚举
|
||||
}
|
||||
|
||||
return null; // 如果不符合任何报警条件,则返回null
|
||||
await Task.Yield();
|
||||
AlarmTask.StartTask(stoppingToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取自定义报警类型
|
||||
/// </summary>
|
||||
/// <param name="tag">要检查的变量</param>
|
||||
/// <param name="limit">报警限制值</param>
|
||||
/// <param name="expressions">报警约束表达式</param>
|
||||
/// <param name="text">报警文本</param>
|
||||
/// <returns>报警类型枚举</returns>
|
||||
private static AlarmTypeEnum? GetCustomAlarmDegree(VariableRuntime tag, out string limit, out string expressions, out string text)
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
limit = string.Empty; // 初始化报警限制值为空字符串
|
||||
expressions = string.Empty; // 初始化报警约束表达式为空字符串
|
||||
text = string.Empty; // 初始化报警文本为空字符串
|
||||
|
||||
if (tag?.Value == null) // 检查变量是否为null或其值为null
|
||||
{
|
||||
return null; // 如果是,则返回null
|
||||
}
|
||||
|
||||
if (tag.CustomAlarmEnable) // 检查是否启用了自定义报警功能
|
||||
{
|
||||
// 调用变量的CustomAlarmCode属性的GetExpressionsResult方法,传入变量的值,获取报警表达式的计算结果
|
||||
var result = tag.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.DeviceRuntime?.Driver?.LogMessage);
|
||||
|
||||
if (result is bool boolResult) // 检查计算结果是否为布尔类型
|
||||
{
|
||||
if (boolResult) // 如果计算结果为true
|
||||
{
|
||||
limit = tag.CustomAlarmCode; // 将报警限制值设置为自定义报警代码
|
||||
expressions = tag.CustomRestrainExpressions!; // 获取自定义报警时的报警约束表达式
|
||||
text = tag.CustomAlarmText!; // 获取自定义报警时的报警文本
|
||||
return AlarmTypeEnum.Custom; // 返回自定义报警类型枚举
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // 如果不符合自定义报警条件,则返回null
|
||||
AlarmTask.Dispose();
|
||||
await base.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取decimal类型的报警类型
|
||||
/// </summary>
|
||||
/// <param name="tag">要检查的变量</param>
|
||||
/// <param name="limit">报警限制值</param>
|
||||
/// <param name="expressions">报警约束表达式</param>
|
||||
/// <param name="text">报警文本</param>
|
||||
/// <returns>报警类型枚举</returns>
|
||||
private static AlarmTypeEnum? GetDecimalAlarmDegree(VariableRuntime tag, out string limit, out string expressions, out string text)
|
||||
{
|
||||
limit = string.Empty; // 初始化报警限制值为空字符串
|
||||
expressions = string.Empty; // 初始化报警约束表达式为空字符串
|
||||
text = string.Empty; // 初始化报警文本为空字符串
|
||||
|
||||
if (tag?.Value == null) // 检查变量是否为null或其值为null
|
||||
{
|
||||
return null; // 如果是,则返回null
|
||||
}
|
||||
|
||||
// 检查是否启用了高高报警功能,并且变量的值大于高高报警的限制值
|
||||
if (tag.HHAlarmEnable && tag.Value.ToDecimal() > tag.HHAlarmCode.ToDecimal())
|
||||
{
|
||||
limit = tag.HHAlarmCode.ToString()!; // 将报警限制值设置为高高报警的限制值
|
||||
expressions = tag.HHRestrainExpressions!; // 获取高高报警的约束表达式
|
||||
text = tag.HHAlarmText!; // 获取高高报警时的报警文本
|
||||
return AlarmTypeEnum.HH; // 返回高高报警类型枚举
|
||||
}
|
||||
|
||||
// 检查是否启用了高报警功能,并且变量的值大于高报警的限制值
|
||||
if (tag.HAlarmEnable && tag.Value.ToDecimal() > tag.HAlarmCode.ToDecimal())
|
||||
{
|
||||
limit = tag.HAlarmCode.ToString()!; // 将报警限制值设置为高报警的限制值
|
||||
expressions = tag.HRestrainExpressions!; // 获取高报警的约束表达式
|
||||
text = tag.HAlarmText!; // 获取高报警时的报警文本
|
||||
return AlarmTypeEnum.H; // 返回高报警类型枚举
|
||||
}
|
||||
|
||||
|
||||
// 检查是否启用了低低报警功能,并且变量的值小于低低报警的限制值
|
||||
if (tag.LLAlarmEnable && tag.Value.ToDecimal() < tag.LLAlarmCode.ToDecimal())
|
||||
{
|
||||
limit = tag.LLAlarmCode.ToString()!; // 将报警限制值设置为低低报警的限制值
|
||||
expressions = tag.LLRestrainExpressions!; // 获取低低报警的约束表达式
|
||||
text = tag.LLAlarmText!; // 获取低低报警时的报警文本
|
||||
return AlarmTypeEnum.LL; // 返回低低报警类型枚举
|
||||
}
|
||||
|
||||
|
||||
// 检查是否启用了低报警功能,并且变量的值小于低报警的限制值
|
||||
if (tag.LAlarmEnable && tag.Value.ToDecimal() < tag.LAlarmCode.ToDecimal())
|
||||
{
|
||||
limit = tag.LAlarmCode.ToString()!; // 将报警限制值设置为低报警的限制值
|
||||
expressions = tag.LRestrainExpressions!; // 获取低报警的约束表达式
|
||||
text = tag.LAlarmText!; // 获取低报警时的报警文本
|
||||
return AlarmTypeEnum.L; // 返回低报警类型枚举
|
||||
}
|
||||
|
||||
return null; // 如果不符合任何报警条件,则返回null
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对变量进行报警分析,并根据需要触发相应的报警事件或恢复事件。
|
||||
/// </summary>
|
||||
/// <param name="item">要进行报警分析的变量</param>
|
||||
private static void AlarmAnalysis(VariableRuntime item)
|
||||
{
|
||||
string limit; // 报警限制值
|
||||
string ex; // 报警约束表达式
|
||||
string text; // 报警文本
|
||||
AlarmTypeEnum? alarmEnum; // 报警类型枚举
|
||||
int delay = item.AlarmDelay; // 获取报警延迟时间
|
||||
|
||||
// 检查变量的数据类型
|
||||
if (item.Value?.GetType() == typeof(bool))
|
||||
{
|
||||
// 如果数据类型为布尔型,则调用GetBoolAlarmCode方法获取布尔型报警类型及相关信息
|
||||
alarmEnum = GetBoolAlarmCode(item, out limit, out ex, out text);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果数据类型为非布尔型,则调用GetDecimalAlarmDegree方法获取数值型报警类型及相关信息
|
||||
alarmEnum = GetDecimalAlarmDegree(item, out limit, out ex, out text);
|
||||
}
|
||||
|
||||
// 如果未获取到报警类型,则尝试获取自定义报警类型
|
||||
if (alarmEnum == null)
|
||||
{
|
||||
alarmEnum = GetCustomAlarmDegree(item, out limit, out ex, out text);
|
||||
}
|
||||
|
||||
if (alarmEnum == null)
|
||||
{
|
||||
// 如果仍未获取到报警类型,则触发需恢复报警事件(如果存在)
|
||||
AlarmChange(item, null, text, EventTypeEnum.Finish, alarmEnum, delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果获取到了报警类型,则需触发报警事件或更新报警状态
|
||||
|
||||
if (!string.IsNullOrEmpty(ex))
|
||||
{
|
||||
// 如果存在报警约束表达式,则计算表达式结果,以确定是否触发报警事件
|
||||
var data = ex.GetExpressionsResult(item.Value, item.DeviceRuntime?.Driver?.LogMessage);
|
||||
if (data is bool result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
// 如果表达式结果为true,则触发报警事件
|
||||
AlarmChange(item, limit, text, EventTypeEnum.Alarm, alarmEnum, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不存在报警约束表达式,则直接触发报警事件
|
||||
AlarmChange(item, limit, text, EventTypeEnum.Alarm, alarmEnum, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据报警事件类型进行相应的处理操作,包括触发报警事件或更新报警状态。
|
||||
/// </summary>
|
||||
/// <param name="item">要处理的变量</param>
|
||||
/// <param name="limit">报警限制值</param>
|
||||
/// <param name="text">报警文本</param>
|
||||
/// <param name="eventEnum">报警事件类型枚举</param>
|
||||
/// <param name="alarmEnum">报警类型枚举</param>
|
||||
/// <param name="delay">报警延时</param>
|
||||
private static void AlarmChange(VariableRuntime item, object limit, string text, EventTypeEnum eventEnum, AlarmTypeEnum? alarmEnum, int delay)
|
||||
{
|
||||
bool changed = false;
|
||||
if (eventEnum == EventTypeEnum.Finish)
|
||||
{
|
||||
// 如果是需恢复报警事件
|
||||
// 如果实时报警列表中不存在该变量,则直接返回
|
||||
if (!GlobalData.RealAlarmIdVariables.ContainsKey(item.Id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (eventEnum == EventTypeEnum.Alarm)
|
||||
{
|
||||
// 如果是触发报警事件
|
||||
// 在实时报警列表中查找该变量
|
||||
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var variable))
|
||||
{
|
||||
// 如果变量已经处于相同的报警类型,则直接返回
|
||||
if (item.AlarmType == alarmEnum)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新变量的报警信息和事件时间
|
||||
if (eventEnum == EventTypeEnum.Alarm)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
//添加报警延时策略
|
||||
if (delay > 0)
|
||||
{
|
||||
if (item.EventType != EventTypeEnum.Alarm && item.EventType != EventTypeEnum.Prepare)
|
||||
{
|
||||
item.EventType = EventTypeEnum.Prepare;//准备报警
|
||||
item.PrepareEventTime = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.EventType == EventTypeEnum.Prepare)
|
||||
{
|
||||
if ((now - item.PrepareEventTime!.Value).TotalSeconds > delay)
|
||||
{
|
||||
//超过延时时间,触发报警
|
||||
item.EventType = EventTypeEnum.Alarm;
|
||||
item.AlarmTime = now;
|
||||
item.EventTime = now;
|
||||
item.AlarmType = alarmEnum;
|
||||
item.AlarmLimit = limit.ToString();
|
||||
item.AlarmCode = item.Value.ToString();
|
||||
item.RecoveryCode = string.Empty;
|
||||
item.AlarmText = text;
|
||||
item.PrepareEventTime = null;
|
||||
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else if (item.EventType == EventTypeEnum.Alarm && item.AlarmType != alarmEnum)
|
||||
{
|
||||
//报警类型改变,重新计时
|
||||
if (item.PrepareEventTime == null)
|
||||
item.PrepareEventTime = now;
|
||||
if ((now - item.PrepareEventTime!.Value).TotalSeconds > delay)
|
||||
{
|
||||
//超过延时时间,触发报警
|
||||
item.EventType = EventTypeEnum.Alarm;
|
||||
item.AlarmTime = now;
|
||||
item.EventTime = now;
|
||||
item.AlarmType = alarmEnum;
|
||||
item.AlarmLimit = limit.ToString();
|
||||
item.AlarmCode = item.Value.ToString();
|
||||
item.RecoveryCode = string.Empty;
|
||||
item.AlarmText = text;
|
||||
item.PrepareEventTime = null;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果是触发报警事件
|
||||
item.EventType = eventEnum;
|
||||
item.AlarmTime = now;
|
||||
item.EventTime = now;
|
||||
item.AlarmType = alarmEnum;
|
||||
item.AlarmLimit = limit.ToString();
|
||||
item.AlarmCode = item.Value.ToString();
|
||||
item.RecoveryCode = string.Empty;
|
||||
item.AlarmText = text;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if (eventEnum == EventTypeEnum.Finish)
|
||||
{
|
||||
// 如果是需恢复报警事件
|
||||
// 获取旧的报警信息
|
||||
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
|
||||
{
|
||||
item.AlarmType = oldAlarm.AlarmType;
|
||||
item.EventType = eventEnum;
|
||||
item.AlarmLimit = oldAlarm.AlarmLimit;
|
||||
item.AlarmCode = oldAlarm.AlarmCode;
|
||||
item.RecoveryCode = item.Value.ToString();
|
||||
item.AlarmText = oldAlarm.AlarmText;
|
||||
item.EventTime = DateTime.Now;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 触发报警变化事件
|
||||
if (changed)
|
||||
{
|
||||
if (item.EventType == EventTypeEnum.Alarm)
|
||||
{
|
||||
// 如果是触发报警事件
|
||||
//lock (GlobalData. RealAlarmVariables)
|
||||
{
|
||||
// 从实时报警列表中移除旧的报警信息,并添加新的报警信息
|
||||
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
|
||||
}
|
||||
}
|
||||
else if (item.EventType == EventTypeEnum.Finish)
|
||||
{
|
||||
// 如果是需恢复报警事件,则从实时报警列表中移除该变量
|
||||
GlobalData.RealAlarmIdVariables.TryRemove(item.Id, out _);
|
||||
//GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
|
||||
}
|
||||
GlobalData.AlarmChange(item.AdaptAlarmVariable());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ConfirmAlarm(long variableId)
|
||||
{
|
||||
// 如果是确认报警事件
|
||||
if (GlobalData.AlarmEnableIdVariables.TryGetValue(variableId, out var variableRuntime))
|
||||
{
|
||||
variableRuntime.EventType = EventTypeEnum.Confirm;
|
||||
variableRuntime.EventTime = DateTime.Now;
|
||||
var data = variableRuntime.AdaptAlarmVariable();
|
||||
GlobalData.RealAlarmIdVariables.AddOrUpdate(variableId, a => data, (a, b) => data);
|
||||
|
||||
GlobalData.AlarmChange(data);
|
||||
}
|
||||
AlarmTask?.ConfirmAlarm(variableId);
|
||||
}
|
||||
|
||||
#endregion 核心实现
|
||||
|
||||
/// <summary>
|
||||
/// 执行工作任务,对设备变量进行报警分析。
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <param name="cancellation">取消任务的 CancellationToken</param>
|
||||
private void DoWork(object? state, CancellationToken cancellation)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
if (!GlobalData.StartBusinessChannelEnable)
|
||||
return;
|
||||
|
||||
//Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
// 遍历设备变量列表
|
||||
|
||||
if (!GlobalData.AlarmEnableIdVariables.IsEmpty)
|
||||
{
|
||||
var list = GlobalData.AlarmEnableIdVariables.Select(a => a.Value).ToArray();
|
||||
list.ParallelForEach((item, state, index) =>
|
||||
{
|
||||
{
|
||||
// 如果取消请求已经被触发,则结束任务
|
||||
if (cancellation.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// 如果该变量的报警功能未启用,则跳过该变量
|
||||
if (!item.AlarmEnable)
|
||||
return;
|
||||
|
||||
// 如果该变量离线,则跳过该变量
|
||||
if (!item.IsOnline)
|
||||
return;
|
||||
|
||||
// 对该变量进行报警分析
|
||||
AlarmAnalysis(item);
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//if (scheduledTask.Period != 5000)
|
||||
// scheduledTask.Change(0, 5000); // 如果没有启用报警的变量,则设置下次执行时间为5秒后
|
||||
scheduledTask.SetNext(5000); // 如果没有启用报警的变量,则设置下次执行时间为5秒后
|
||||
}
|
||||
|
||||
//stopwatch.Stop();
|
||||
//_logger.LogInformation("报警分析耗时:" + stopwatch.ElapsedMilliseconds + "ms");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Alarm analysis fail");
|
||||
}
|
||||
}
|
||||
|
||||
private ScheduledSyncTask scheduledTask;
|
||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation(AppResource.RealAlarmTaskStart);
|
||||
scheduledTask = new ScheduledSyncTask(10, DoWork, null, null, stoppingToken);
|
||||
scheduledTask.Start();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,475 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 设备采集报警后台服务
|
||||
/// </summary>
|
||||
internal sealed class AlarmTask : IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private ScheduledSyncTask scheduledTask;
|
||||
public AlarmTask(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void StartTask(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(AppResource.RealAlarmTaskStart);
|
||||
scheduledTask = new ScheduledSyncTask(10, DoWork, null, null, cancellationToken);
|
||||
scheduledTask.Start();
|
||||
}
|
||||
|
||||
public void StopTask()
|
||||
{
|
||||
_logger.LogInformation(AppResource.RealAlarmTaskStop);
|
||||
scheduledTask?.Stop();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
StopTask();
|
||||
scheduledTask?.TryDispose();
|
||||
}
|
||||
|
||||
|
||||
#region 核心实现
|
||||
|
||||
/// <summary>
|
||||
/// 获取bool报警类型
|
||||
/// </summary>
|
||||
/// <param name="tag">要检查的变量</param>
|
||||
/// <param name="limit">报警限制值</param>
|
||||
/// <param name="expressions">报警约束表达式</param>
|
||||
/// <param name="text">报警文本</param>
|
||||
/// <returns>报警类型枚举</returns>
|
||||
private static AlarmTypeEnum? GetBoolAlarmCode(VariableRuntime tag, out string limit, out string expressions, out string text)
|
||||
{
|
||||
limit = string.Empty; // 初始化报警限制值为空字符串
|
||||
expressions = string.Empty; // 初始化报警约束表达式为空字符串
|
||||
text = string.Empty; // 初始化报警文本为空字符串
|
||||
|
||||
if (tag?.Value == null) // 检查变量是否为null或其值为null
|
||||
{
|
||||
return null; // 如果是,则返回null
|
||||
}
|
||||
|
||||
if (tag.BoolCloseAlarmEnable && !tag.Value.ToBoolean(true)) // 检查是否启用了关闭报警功能,并且变量的布尔值为false
|
||||
{
|
||||
limit = false.ToString(); // 将报警限制值设置为"false"
|
||||
expressions = tag.BoolCloseRestrainExpressions!; // 获取关闭报警的约束表达式
|
||||
text = tag.BoolCloseAlarmText!; // 获取关闭报警时的报警文本
|
||||
return AlarmTypeEnum.Close; // 返回关闭报警类型枚举
|
||||
}
|
||||
|
||||
if (tag.BoolOpenAlarmEnable && tag.Value.ToBoolean(false)) // 检查是否启用了开启报警功能,并且变量的布尔值为true
|
||||
{
|
||||
limit = true.ToString(); // 将报警限制值设置为"true"
|
||||
expressions = tag.BoolOpenRestrainExpressions!; // 获取开启报警的约束表达式
|
||||
text = tag.BoolOpenAlarmText!; // 获取开启报警时的报警文本
|
||||
return AlarmTypeEnum.Open; // 返回开启报警类型枚举
|
||||
}
|
||||
|
||||
return null; // 如果不符合任何报警条件,则返回null
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取自定义报警类型
|
||||
/// </summary>
|
||||
/// <param name="tag">要检查的变量</param>
|
||||
/// <param name="limit">报警限制值</param>
|
||||
/// <param name="expressions">报警约束表达式</param>
|
||||
/// <param name="text">报警文本</param>
|
||||
/// <returns>报警类型枚举</returns>
|
||||
private static AlarmTypeEnum? GetCustomAlarmDegree(VariableRuntime tag, out string limit, out string expressions, out string text)
|
||||
{
|
||||
limit = string.Empty; // 初始化报警限制值为空字符串
|
||||
expressions = string.Empty; // 初始化报警约束表达式为空字符串
|
||||
text = string.Empty; // 初始化报警文本为空字符串
|
||||
|
||||
if (tag?.Value == null) // 检查变量是否为null或其值为null
|
||||
{
|
||||
return null; // 如果是,则返回null
|
||||
}
|
||||
|
||||
if (tag.CustomAlarmEnable) // 检查是否启用了自定义报警功能
|
||||
{
|
||||
// 调用变量的CustomAlarmCode属性的GetExpressionsResult方法,传入变量的值,获取报警表达式的计算结果
|
||||
var result = tag.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.DeviceRuntime?.Driver?.LogMessage);
|
||||
|
||||
if (result is bool boolResult) // 检查计算结果是否为布尔类型
|
||||
{
|
||||
if (boolResult) // 如果计算结果为true
|
||||
{
|
||||
limit = tag.CustomAlarmCode; // 将报警限制值设置为自定义报警代码
|
||||
expressions = tag.CustomRestrainExpressions!; // 获取自定义报警时的报警约束表达式
|
||||
text = tag.CustomAlarmText!; // 获取自定义报警时的报警文本
|
||||
return AlarmTypeEnum.Custom; // 返回自定义报警类型枚举
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // 如果不符合自定义报警条件,则返回null
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取decimal类型的报警类型
|
||||
/// </summary>
|
||||
/// <param name="tag">要检查的变量</param>
|
||||
/// <param name="limit">报警限制值</param>
|
||||
/// <param name="expressions">报警约束表达式</param>
|
||||
/// <param name="text">报警文本</param>
|
||||
/// <returns>报警类型枚举</returns>
|
||||
private static AlarmTypeEnum? GetDecimalAlarmDegree(VariableRuntime tag, out string limit, out string expressions, out string text)
|
||||
{
|
||||
limit = string.Empty; // 初始化报警限制值为空字符串
|
||||
expressions = string.Empty; // 初始化报警约束表达式为空字符串
|
||||
text = string.Empty; // 初始化报警文本为空字符串
|
||||
|
||||
if (tag?.Value == null) // 检查变量是否为null或其值为null
|
||||
{
|
||||
return null; // 如果是,则返回null
|
||||
}
|
||||
|
||||
// 检查是否启用了高高报警功能,并且变量的值大于高高报警的限制值
|
||||
if (tag.HHAlarmEnable && tag.Value.ToDecimal() > tag.HHAlarmCode.ToDecimal())
|
||||
{
|
||||
limit = tag.HHAlarmCode.ToString()!; // 将报警限制值设置为高高报警的限制值
|
||||
expressions = tag.HHRestrainExpressions!; // 获取高高报警的约束表达式
|
||||
text = tag.HHAlarmText!; // 获取高高报警时的报警文本
|
||||
return AlarmTypeEnum.HH; // 返回高高报警类型枚举
|
||||
}
|
||||
|
||||
// 检查是否启用了高报警功能,并且变量的值大于高报警的限制值
|
||||
if (tag.HAlarmEnable && tag.Value.ToDecimal() > tag.HAlarmCode.ToDecimal())
|
||||
{
|
||||
limit = tag.HAlarmCode.ToString()!; // 将报警限制值设置为高报警的限制值
|
||||
expressions = tag.HRestrainExpressions!; // 获取高报警的约束表达式
|
||||
text = tag.HAlarmText!; // 获取高报警时的报警文本
|
||||
return AlarmTypeEnum.H; // 返回高报警类型枚举
|
||||
}
|
||||
|
||||
|
||||
// 检查是否启用了低低报警功能,并且变量的值小于低低报警的限制值
|
||||
if (tag.LLAlarmEnable && tag.Value.ToDecimal() < tag.LLAlarmCode.ToDecimal())
|
||||
{
|
||||
limit = tag.LLAlarmCode.ToString()!; // 将报警限制值设置为低低报警的限制值
|
||||
expressions = tag.LLRestrainExpressions!; // 获取低低报警的约束表达式
|
||||
text = tag.LLAlarmText!; // 获取低低报警时的报警文本
|
||||
return AlarmTypeEnum.LL; // 返回低低报警类型枚举
|
||||
}
|
||||
|
||||
|
||||
// 检查是否启用了低报警功能,并且变量的值小于低报警的限制值
|
||||
if (tag.LAlarmEnable && tag.Value.ToDecimal() < tag.LAlarmCode.ToDecimal())
|
||||
{
|
||||
limit = tag.LAlarmCode.ToString()!; // 将报警限制值设置为低报警的限制值
|
||||
expressions = tag.LRestrainExpressions!; // 获取低报警的约束表达式
|
||||
text = tag.LAlarmText!; // 获取低报警时的报警文本
|
||||
return AlarmTypeEnum.L; // 返回低报警类型枚举
|
||||
}
|
||||
|
||||
return null; // 如果不符合任何报警条件,则返回null
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对变量进行报警分析,并根据需要触发相应的报警事件或恢复事件。
|
||||
/// </summary>
|
||||
/// <param name="item">要进行报警分析的变量</param>
|
||||
private static void AlarmAnalysis(VariableRuntime item)
|
||||
{
|
||||
string limit; // 报警限制值
|
||||
string ex; // 报警约束表达式
|
||||
string text; // 报警文本
|
||||
AlarmTypeEnum? alarmEnum; // 报警类型枚举
|
||||
int delay = item.AlarmDelay; // 获取报警延迟时间
|
||||
|
||||
// 检查变量的数据类型
|
||||
if (item.Value?.GetType() == typeof(bool))
|
||||
{
|
||||
// 如果数据类型为布尔型,则调用GetBoolAlarmCode方法获取布尔型报警类型及相关信息
|
||||
alarmEnum = GetBoolAlarmCode(item, out limit, out ex, out text);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果数据类型为非布尔型,则调用GetDecimalAlarmDegree方法获取数值型报警类型及相关信息
|
||||
alarmEnum = GetDecimalAlarmDegree(item, out limit, out ex, out text);
|
||||
}
|
||||
|
||||
// 如果未获取到报警类型,则尝试获取自定义报警类型
|
||||
if (alarmEnum == null)
|
||||
{
|
||||
alarmEnum = GetCustomAlarmDegree(item, out limit, out ex, out text);
|
||||
}
|
||||
|
||||
if (alarmEnum == null)
|
||||
{
|
||||
// 如果仍未获取到报警类型,则触发需恢复报警事件(如果存在)
|
||||
AlarmChange(item, null, text, EventTypeEnum.Finish, alarmEnum, delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果获取到了报警类型,则需触发报警事件或更新报警状态
|
||||
|
||||
if (!string.IsNullOrEmpty(ex))
|
||||
{
|
||||
// 如果存在报警约束表达式,则计算表达式结果,以确定是否触发报警事件
|
||||
var data = ex.GetExpressionsResult(item.Value, item.DeviceRuntime?.Driver?.LogMessage);
|
||||
if (data is bool result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
// 如果表达式结果为true,则触发报警事件
|
||||
AlarmChange(item, limit, text, EventTypeEnum.Alarm, alarmEnum, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不存在报警约束表达式,则直接触发报警事件
|
||||
AlarmChange(item, limit, text, EventTypeEnum.Alarm, alarmEnum, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据报警事件类型进行相应的处理操作,包括触发报警事件或更新报警状态。
|
||||
/// </summary>
|
||||
/// <param name="item">要处理的变量</param>
|
||||
/// <param name="limit">报警限制值</param>
|
||||
/// <param name="text">报警文本</param>
|
||||
/// <param name="eventEnum">报警事件类型枚举</param>
|
||||
/// <param name="alarmEnum">报警类型枚举</param>
|
||||
/// <param name="delay">报警延时</param>
|
||||
private static void AlarmChange(VariableRuntime item, object limit, string text, EventTypeEnum eventEnum, AlarmTypeEnum? alarmEnum, int delay)
|
||||
{
|
||||
bool changed = false;
|
||||
if (eventEnum == EventTypeEnum.Finish)
|
||||
{
|
||||
// 如果是需恢复报警事件
|
||||
// 如果实时报警列表中不存在该变量,则直接返回
|
||||
if (!GlobalData.RealAlarmIdVariables.ContainsKey(item.Id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (eventEnum == EventTypeEnum.Alarm)
|
||||
{
|
||||
// 如果是触发报警事件
|
||||
// 在实时报警列表中查找该变量
|
||||
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var variable))
|
||||
{
|
||||
// 如果变量已经处于相同的报警类型,则直接返回
|
||||
if (item.AlarmType == alarmEnum)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新变量的报警信息和事件时间
|
||||
if (eventEnum == EventTypeEnum.Alarm)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
//添加报警延时策略
|
||||
if (delay > 0)
|
||||
{
|
||||
if (item.EventType != EventTypeEnum.Alarm && item.EventType != EventTypeEnum.Prepare)
|
||||
{
|
||||
item.EventType = EventTypeEnum.Prepare;//准备报警
|
||||
item.PrepareEventTime = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.EventType == EventTypeEnum.Prepare)
|
||||
{
|
||||
if ((now - item.PrepareEventTime!.Value).TotalSeconds > delay)
|
||||
{
|
||||
//超过延时时间,触发报警
|
||||
item.EventType = EventTypeEnum.Alarm;
|
||||
item.AlarmTime = now;
|
||||
item.EventTime = now;
|
||||
item.AlarmType = alarmEnum;
|
||||
item.AlarmLimit = limit.ToString();
|
||||
item.AlarmCode = item.Value.ToString();
|
||||
item.RecoveryCode = string.Empty;
|
||||
item.AlarmText = text;
|
||||
item.PrepareEventTime = null;
|
||||
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else if (item.EventType == EventTypeEnum.Alarm && item.AlarmType != alarmEnum)
|
||||
{
|
||||
//报警类型改变,重新计时
|
||||
if (item.PrepareEventTime == null)
|
||||
item.PrepareEventTime = now;
|
||||
if ((now - item.PrepareEventTime!.Value).TotalSeconds > delay)
|
||||
{
|
||||
//超过延时时间,触发报警
|
||||
item.EventType = EventTypeEnum.Alarm;
|
||||
item.AlarmTime = now;
|
||||
item.EventTime = now;
|
||||
item.AlarmType = alarmEnum;
|
||||
item.AlarmLimit = limit.ToString();
|
||||
item.AlarmCode = item.Value.ToString();
|
||||
item.RecoveryCode = string.Empty;
|
||||
item.AlarmText = text;
|
||||
item.PrepareEventTime = null;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果是触发报警事件
|
||||
item.EventType = eventEnum;
|
||||
item.AlarmTime = now;
|
||||
item.EventTime = now;
|
||||
item.AlarmType = alarmEnum;
|
||||
item.AlarmLimit = limit.ToString();
|
||||
item.AlarmCode = item.Value.ToString();
|
||||
item.RecoveryCode = string.Empty;
|
||||
item.AlarmText = text;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if (eventEnum == EventTypeEnum.Finish)
|
||||
{
|
||||
// 如果是需恢复报警事件
|
||||
// 获取旧的报警信息
|
||||
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
|
||||
{
|
||||
item.AlarmType = oldAlarm.AlarmType;
|
||||
item.EventType = eventEnum;
|
||||
item.AlarmLimit = oldAlarm.AlarmLimit;
|
||||
item.AlarmCode = oldAlarm.AlarmCode;
|
||||
item.RecoveryCode = item.Value.ToString();
|
||||
item.AlarmText = oldAlarm.AlarmText;
|
||||
item.EventTime = DateTime.Now;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 触发报警变化事件
|
||||
if (changed)
|
||||
{
|
||||
if (item.EventType == EventTypeEnum.Alarm)
|
||||
{
|
||||
// 如果是触发报警事件
|
||||
//lock (GlobalData. RealAlarmVariables)
|
||||
{
|
||||
// 从实时报警列表中移除旧的报警信息,并添加新的报警信息
|
||||
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
|
||||
}
|
||||
}
|
||||
else if (item.EventType == EventTypeEnum.Finish)
|
||||
{
|
||||
// 如果是需恢复报警事件,则从实时报警列表中移除该变量
|
||||
GlobalData.RealAlarmIdVariables.TryRemove(item.Id, out _);
|
||||
//GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
|
||||
}
|
||||
GlobalData.AlarmChange(item.AdaptAlarmVariable());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ConfirmAlarm(long variableId)
|
||||
{
|
||||
// 如果是确认报警事件
|
||||
if (GlobalData.AlarmEnableIdVariables.TryGetValue(variableId, out var variableRuntime))
|
||||
{
|
||||
variableRuntime.EventType = EventTypeEnum.Confirm;
|
||||
variableRuntime.EventTime = DateTime.Now;
|
||||
GlobalData.RealAlarmIdVariables.AddOrUpdate(variableId, a => variableRuntime.AdaptAlarmVariable(), (a, b) => variableRuntime.AdaptAlarmVariable());
|
||||
GlobalData.AlarmChange(variableRuntime.AdaptAlarmVariable());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 核心实现
|
||||
|
||||
/// <summary>
|
||||
/// 执行工作任务,对设备变量进行报警分析。
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <param name="cancellation">取消任务的 CancellationToken</param>
|
||||
private void DoWork(object? state, CancellationToken cancellation)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
if (!GlobalData.StartBusinessChannelEnable)
|
||||
return;
|
||||
|
||||
//Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
// 遍历设备变量列表
|
||||
|
||||
if (!GlobalData.AlarmEnableIdVariables.IsEmpty)
|
||||
{
|
||||
var list = GlobalData.AlarmEnableIdVariables.Select(a => a.Value).ToArray();
|
||||
list.ParallelForEach((item, state, index) =>
|
||||
{
|
||||
{
|
||||
// 如果取消请求已经被触发,则结束任务
|
||||
if (cancellation.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// 如果该变量的报警功能未启用,则跳过该变量
|
||||
if (!item.AlarmEnable)
|
||||
return;
|
||||
|
||||
// 如果该变量离线,则跳过该变量
|
||||
if (!item.IsOnline)
|
||||
return;
|
||||
|
||||
// 对该变量进行报警分析
|
||||
AlarmAnalysis(item);
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//if (scheduledTask.Period != 5000)
|
||||
// scheduledTask.Change(0, 5000); // 如果没有启用报警的变量,则设置下次执行时间为5秒后
|
||||
scheduledTask.SetNext(5000); // 如果没有启用报警的变量,则设置下次执行时间为5秒后
|
||||
}
|
||||
|
||||
//stopwatch.Stop();
|
||||
//_logger.LogInformation("报警分析耗时:" + stopwatch.ElapsedMilliseconds + "ms");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Alarm analysis fail");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -644,7 +644,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
|
||||
//找出新的通道,添加设备线程
|
||||
|
||||
if (!GlobalData.Channels.TryGetValue(newDeviceRuntime.ChannelId, out var channelRuntime))
|
||||
if (!GlobalData.IdChannels.TryGetValue(newDeviceRuntime.ChannelId, out var channelRuntime))
|
||||
LogMessage?.LogWarning($"device {newDeviceRuntime.Name} cannot found channel with id{newDeviceRuntime.ChannelId}");
|
||||
|
||||
newDeviceRuntime.Init(channelRuntime);
|
||||
|
@@ -23,7 +23,7 @@ internal static class ManageHelper
|
||||
|
||||
public static void CheckChannelCount(int addCount)
|
||||
{
|
||||
var data = GlobalData.Channels.Count + addCount;
|
||||
var data = GlobalData.IdChannels.Count + addCount;
|
||||
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
|
||||
if (data > ManageHelper.ChannelThreadOptions.MaxChannelCount || data > authorizeInfo?.MaxChannelCount)
|
||||
{
|
||||
|
@@ -15,9 +15,9 @@ namespace ThingsGateway.Management;
|
||||
|
||||
public interface IRedundancyHostedService : IHostedService
|
||||
{
|
||||
Task<OperResult> StartRedundancyTaskAsync();
|
||||
Task StopRedundancyTaskAsync();
|
||||
ValueTask ForcedSync(CancellationToken cancellationToken = default);
|
||||
Task StartTaskAsync(CancellationToken cancellationToken);
|
||||
Task StopTaskAsync();
|
||||
Task ForcedSync(CancellationToken cancellationToken = default);
|
||||
|
||||
public TextFileLogger TextLogger { get; }
|
||||
public string LogPath { get; }
|
||||
|
@@ -9,640 +9,40 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHostedService, IRpcDriver
|
||||
internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHostedService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IRedundancyService _redundancyService;
|
||||
private readonly GatewayRedundantSerivce _gatewayRedundantSerivce;
|
||||
/// <inheritdoc cref="RedundancyHostedService"/>
|
||||
public RedundancyHostedService(ILogger<RedundancyHostedService> logger, IStringLocalizer<RedundancyHostedService> localizer, IRedundancyService redundancyService, GatewayRedundantSerivce gatewayRedundantSerivce)
|
||||
public RedundancyHostedService(ILogger<RedundancyHostedService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
Localizer = localizer;
|
||||
_gatewayRedundantSerivce = gatewayRedundantSerivce;
|
||||
// 创建新的文件日志记录器,并设置日志级别为 Trace
|
||||
LogPath = "Logs/RedundancyLog";
|
||||
TextLogger = TextFileLogger.GetMultipleFileLogger(LogPath);
|
||||
TextLogger.LogLevel = TouchSocket.Core.LogLevel.Trace;
|
||||
|
||||
_redundancyService = redundancyService;
|
||||
RedundancyTask = new RedundancyTask(_logger);
|
||||
}
|
||||
public override void Dispose()
|
||||
{
|
||||
TextLogger.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
private IStringLocalizer Localizer { get; }
|
||||
private DoTask RedundancyTask { get; set; }
|
||||
private WaitLock RedundancyRestartLock { get; } = new();
|
||||
public ILog LogMessage { get; set; }
|
||||
public TextFileLogger TextLogger { get; }
|
||||
public string LogPath { get; }
|
||||
private TcpDmtpClient TcpDmtpClient;
|
||||
private TcpDmtpService TcpDmtpService;
|
||||
private async Task<TcpDmtpClient> GetTcpDmtpClient(RedundancyOptions redundancy)
|
||||
{
|
||||
var log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
log?.AddLogger(TextLogger);
|
||||
LogMessage = log;
|
||||
var tcpDmtpClient = new TcpDmtpClient();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetRemoteIPHost(redundancy.MasterUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = redundancy.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||
.SetMaxFailCount(redundancy.MaxErrorCount);
|
||||
});
|
||||
private RedundancyTask RedundancyTask;
|
||||
|
||||
await tcpDmtpClient.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpDmtpClient;
|
||||
}
|
||||
public TextFileLogger TextLogger => RedundancyTask.TextLogger;
|
||||
|
||||
private async Task<TcpDmtpService> GetTcpDmtpService(RedundancyOptions redundancy)
|
||||
{
|
||||
var log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
log?.AddLogger(TextLogger);
|
||||
LogMessage = log;
|
||||
var tcpDmtpService = new TcpDmtpService();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetListenIPHosts(redundancy.MasterUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = redundancy.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||
.SetMaxFailCount(redundancy.MaxErrorCount);
|
||||
});
|
||||
public string LogPath => RedundancyTask.LogPath;
|
||||
|
||||
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpDmtpService;
|
||||
}
|
||||
|
||||
private void Log_Out(TouchSocket.Core.LogLevel logLevel, object source, string message, Exception exception)
|
||||
{
|
||||
_logger?.Log_Out(logLevel, source, message, exception);
|
||||
}
|
||||
|
||||
private static Task RestartAsync()
|
||||
{
|
||||
return GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.ReadOnlyChannels.Values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主站
|
||||
/// </summary>
|
||||
private async Task DoMasterWork(object? state, CancellationToken stoppingToken)
|
||||
{
|
||||
// 延迟一段时间,避免过于频繁地执行任务
|
||||
await Task.Delay(500, stoppingToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = stoppingToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (TcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
var deviceRunTimes = GlobalData.ReadOnlyIdDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).AdaptListDeviceDataWithValue();
|
||||
|
||||
foreach (var item in TcpDmtpService.Clients)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
await item.GetDmtpRpcActor().InvokeAsync(
|
||||
nameof(ReverseCallbackServer.UpData), null, waitInvoke, deviceRunTimes).ConfigureAwait(false);
|
||||
LogMessage?.LogTrace($"{item.GetIPPort()} Update StandbyStation data success");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "Synchronize data to standby site error");
|
||||
}
|
||||
await Task.Delay(RedundancyOptions.SyncInterval, stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Execute");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从站
|
||||
/// </summary>
|
||||
private async Task DoSlaveWork(object? state, CancellationToken stoppingToken)
|
||||
{
|
||||
// 延迟一段时间,避免过于频繁地执行任务
|
||||
await Task.Delay(5000, stoppingToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = stoppingToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await TcpDmtpClient.TryConnectAsync().ConfigureAwait(false);
|
||||
|
||||
{
|
||||
// 初始化读取错误计数器
|
||||
var readErrorCount = 0;
|
||||
// 当读取错误次数小于最大错误计数时循环执行
|
||||
while (readErrorCount < RedundancyOptions.MaxErrorCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 发送 Ping 请求以检查设备是否在线,超时时间为 10000 毫秒
|
||||
online = await TcpDmtpClient.PingAsync(10000).ConfigureAwait(false);
|
||||
if (online)
|
||||
break;
|
||||
else
|
||||
{
|
||||
readErrorCount++;
|
||||
await Task.Delay(RedundancyOptions.SyncInterval, stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 捕获异常,增加读取错误计数器
|
||||
readErrorCount++;
|
||||
await Task.Delay(RedundancyOptions.SyncInterval, stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果设备不在线
|
||||
if (!online)
|
||||
{
|
||||
// 无法获取状态,启动本机
|
||||
await ActiveAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果设备在线
|
||||
LogMessage?.LogTrace($"Ping ActiveStation {RedundancyOptions.MasterUri} success");
|
||||
await StandbyAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Execute");
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ForcedSync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
online = (await TcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
|
||||
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
var data = await TcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(
|
||||
nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false);
|
||||
|
||||
await GlobalData.ChannelRuntimeService.CopyAsync(data.Select(a => a.Channel).ToList(), data.SelectMany(a => a.DeviceVariables).ToDictionary(a => a.Device, a => a.Variables), true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogTrace($"ForcedSync data success");
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "ForcedSync data error");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Execute");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private WaitLock _switchLock = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
await StartRedundancyTaskAsync().ConfigureAwait(false);
|
||||
await RedundancyTask.StartTaskAsync(stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
public Task StartTaskAsync(CancellationToken cancellationToken) => RedundancyTask.StartTaskAsync(cancellationToken);
|
||||
public Task StopTaskAsync() => RedundancyTask.StopTaskAsync();
|
||||
|
||||
public async Task<OperResult> StartRedundancyTaskAsync()
|
||||
public Task ForcedSync(CancellationToken cancellationToken = default) => RedundancyTask.ForcedSync(cancellationToken);
|
||||
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RedundancyRestartLock.WaitAsync().ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||||
|
||||
if (RedundancyTask != null)
|
||||
{
|
||||
await RedundancyTask.StopAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false); // 停止现有任务,等待最多30秒钟
|
||||
}
|
||||
await BeforeStartAsync().ConfigureAwait(false);
|
||||
if (RedundancyOptions?.Enable == true)
|
||||
{
|
||||
if (RedundancyOptions.IsMaster)
|
||||
{
|
||||
RedundancyTask = new DoTask(DoMasterWork, LogMessage); // 创建新的任务
|
||||
}
|
||||
else
|
||||
{
|
||||
RedundancyTask = new DoTask(DoSlaveWork, LogMessage); // 创建新的任务
|
||||
}
|
||||
|
||||
RedundancyTask?.Start(default); // 启动任务
|
||||
}
|
||||
|
||||
return new();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogError(ex, "Start"); // 记录错误日志
|
||||
return new(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
RedundancyRestartLock.Release(); // 释放锁
|
||||
}
|
||||
await RedundancyTask.DisposeAsync().ConfigureAwait(false);
|
||||
await base.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private RedundancyOptions RedundancyOptions;
|
||||
|
||||
private async Task BeforeStartAsync()
|
||||
{
|
||||
RedundancyOptions = (await _redundancyService.GetRedundancyAsync().ConfigureAwait(false)).AdaptRedundancyOptions();
|
||||
|
||||
if (RedundancyOptions?.Enable == true)
|
||||
{
|
||||
if (RedundancyOptions.IsMaster)
|
||||
{
|
||||
TcpDmtpService = await GetTcpDmtpService(RedundancyOptions).ConfigureAwait(false);
|
||||
|
||||
await TcpDmtpService.StartAsync().ConfigureAwait(false);//启动
|
||||
await ActiveAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
TcpDmtpClient = await GetTcpDmtpClient(RedundancyOptions).ConfigureAwait(false);
|
||||
await StandbyAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ActiveAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
private bool first;
|
||||
private async Task StandbyAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _switchLock.WaitAsync().ConfigureAwait(false);
|
||||
if (_gatewayRedundantSerivce.StartCollectChannelEnable)
|
||||
{
|
||||
// 输出日志,指示主站已恢复,从站将切换到备用状态
|
||||
if (first)
|
||||
LogMessage?.Warning("Master site has recovered, local machine (standby) will switch to standby state");
|
||||
|
||||
// 将 IsStart 设置为 false,表示当前设备为从站,切换到备用状态
|
||||
_gatewayRedundantSerivce.StartCollectChannelEnable = false;
|
||||
_gatewayRedundantSerivce.StartBusinessChannelEnable = RedundancyOptions?.IsStartBusinessDevice ?? false;
|
||||
await RestartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_switchLock.Release();
|
||||
first = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ActiveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _switchLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
_gatewayRedundantSerivce.StartBusinessChannelEnable = true;
|
||||
if (!_gatewayRedundantSerivce.StartCollectChannelEnable)
|
||||
{
|
||||
// 输出日志,指示无法连接冗余站点,本机将切换到正常状态
|
||||
if (first)
|
||||
LogMessage?.Warning("Cannot connect to redundant site, local machine will switch to normal state");
|
||||
_gatewayRedundantSerivce.StartCollectChannelEnable = true;
|
||||
await RestartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_switchLock.Release();
|
||||
first = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopRedundancyTaskAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await RedundancyRestartLock.WaitAsync().ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||||
|
||||
if (RedundancyTask != null)
|
||||
{
|
||||
await RedundancyTask.StopAsync(TimeSpan.FromSeconds(10)).ConfigureAwait(false); // 停止任务,等待最多10秒钟
|
||||
}
|
||||
if (TcpDmtpService != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await TcpDmtpService.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
if (TcpDmtpClient != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await TcpDmtpClient.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
TcpDmtpService?.Dispose();
|
||||
TcpDmtpClient?.Dispose();
|
||||
RedundancyTask = null;
|
||||
TcpDmtpService = null;
|
||||
TcpDmtpClient = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogError(ex, "Stop"); // 记录错误日志
|
||||
}
|
||||
finally
|
||||
{
|
||||
first = false;
|
||||
RedundancyRestartLock.Release(); // 释放锁
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
Dictionary<string, Dictionary<string, OperResult<object>>> dataResult = new();
|
||||
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas = new();
|
||||
foreach (var item in writeInfoLists)
|
||||
{
|
||||
if (deviceDatas.TryGetValue(item.Key.DeviceName ?? string.Empty, out var variableDatas))
|
||||
{
|
||||
variableDatas.Add(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceDatas.Add(item.Key.DeviceName ?? string.Empty, new());
|
||||
deviceDatas[item.Key.DeviceName ?? string.Empty].Add(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
if (RedundancyOptions.IsMaster)
|
||||
{
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (!RedundancyOptions.IsMaster)
|
||||
{
|
||||
online = (await TcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
dataResult = await TcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), waitInvoke, deviceDatas).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogTrace($"Rpc success");
|
||||
|
||||
return dataResult;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(item.Key, out var device))
|
||||
{
|
||||
var key = device.Tag;
|
||||
|
||||
if (TcpDmtpService.TryGetClient(key, out var client))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var data = await TcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), waitInvoke, new Dictionary<string, Dictionary<string, string>>() { { item.Key, item.Value } }).ConfigureAwait(false);
|
||||
|
||||
dataResult.AddRange(data);
|
||||
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].Add(vItem.Key, new OperResult<object>(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].Add(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
|
||||
LogMessage?.LogTrace($"Rpc success");
|
||||
return dataResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage?.LogWarning("Rpc error, no client online");
|
||||
}
|
||||
}
|
||||
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "Rpc error");
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, Dictionary<string, OperResult<object>>> NoOnline(Dictionary<string, Dictionary<string, OperResult<object>>> dataResult, Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
return dataResult;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,10 @@ public class RedundancyOptions
|
||||
/// 获取或设置是否为主设备。
|
||||
/// </summary>
|
||||
public bool IsMaster { get; set; }
|
||||
|
||||
//主站只建议是服务端
|
||||
//[DynamicProperty]
|
||||
//public bool IsServer { get; set; } = true;
|
||||
internal bool IsServer => IsMaster;
|
||||
/// <summary>
|
||||
/// 获取或设置用于验证的令牌。
|
||||
/// </summary>
|
||||
|
@@ -0,0 +1,652 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人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 Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IRedundancyService _redundancyService;
|
||||
private readonly GatewayRedundantSerivce _gatewayRedundantSerivce;
|
||||
|
||||
public RedundancyTask(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_gatewayRedundantSerivce = App.RootServices.GetRequiredService<GatewayRedundantSerivce>();
|
||||
_redundancyService = App.RootServices.GetRequiredService<IRedundancyService>();
|
||||
// 创建新的文件日志记录器,并设置日志级别为 Trace
|
||||
LogPath = "Logs/RedundancyLog";
|
||||
TextLogger = TextFileLogger.GetMultipleFileLogger(LogPath);
|
||||
TextLogger.LogLevel = TouchSocket.Core.LogLevel.Trace;
|
||||
}
|
||||
|
||||
|
||||
public ILog LogMessage { get; set; }
|
||||
public TextFileLogger TextLogger { get; }
|
||||
public string LogPath { get; }
|
||||
private TcpDmtpClient _tcpDmtpClient;
|
||||
private TcpDmtpService _tcpDmtpService;
|
||||
|
||||
private void Log_Out(TouchSocket.Core.LogLevel logLevel, object source, string message, Exception exception)
|
||||
{
|
||||
_logger?.Log_Out(logLevel, source, message, exception);
|
||||
}
|
||||
|
||||
private ScheduledAsyncTask scheduledTask;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 主站
|
||||
/// </summary>
|
||||
private async Task DoMasterWork(object? state, CancellationToken stoppingToken)
|
||||
{
|
||||
// 延迟一段时间,避免过于频繁地执行任务
|
||||
await Task.Delay(500, stoppingToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
bool online = false;
|
||||
|
||||
var waitInvoke = CreateDmtpInvokeOption(stoppingToken);
|
||||
|
||||
try
|
||||
{
|
||||
if (_tcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
|
||||
int batchSize = 10000;
|
||||
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory > 2 * 1024 * 1024)
|
||||
{
|
||||
batchSize = 200000;
|
||||
}
|
||||
var deviceRunTimes = GlobalData.ReadOnlyIdDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).Batch(batchSize);
|
||||
|
||||
|
||||
foreach (var item in _tcpDmtpService.Clients)
|
||||
{
|
||||
foreach (var deviceDataWithValues in deviceRunTimes)
|
||||
{
|
||||
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
await item.GetDmtpRpcActor().InvokeAsync(
|
||||
nameof(ReverseCallbackServer.UpData), null, waitInvoke, deviceDataWithValues.AdaptListDeviceDataWithValue()).ConfigureAwait(false);
|
||||
}
|
||||
LogMessage?.LogTrace($"{item.GetIPPort()} Update StandbyStation data success");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "Synchronize data to standby site error");
|
||||
}
|
||||
await Task.Delay(RedundancyOptions.SyncInterval, stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Execute");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从站
|
||||
/// </summary>
|
||||
private async Task DoSlaveWork(object? state, CancellationToken stoppingToken)
|
||||
{
|
||||
// 延迟一段时间,避免过于频繁地执行任务
|
||||
await Task.Delay(5000, stoppingToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
bool online = false;
|
||||
var waitInvoke = CreateDmtpInvokeOption(stoppingToken);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
await _tcpDmtpClient.TryConnectAsync().ConfigureAwait(false);
|
||||
|
||||
{
|
||||
// 初始化读取错误计数器
|
||||
var readErrorCount = 0;
|
||||
// 当读取错误次数小于最大错误计数时循环执行
|
||||
while (readErrorCount < RedundancyOptions.MaxErrorCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 发送 Ping 请求以检查设备是否在线,超时时间为 10000 毫秒
|
||||
online = await _tcpDmtpClient.PingAsync(10000).ConfigureAwait(false);
|
||||
if (online)
|
||||
break;
|
||||
else
|
||||
{
|
||||
readErrorCount++;
|
||||
await Task.Delay(RedundancyOptions.SyncInterval, stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 捕获异常,增加读取错误计数器
|
||||
readErrorCount++;
|
||||
await Task.Delay(RedundancyOptions.SyncInterval, stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果设备不在线
|
||||
if (!online)
|
||||
{
|
||||
// 无法获取状态,启动本机
|
||||
await ActiveAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果设备在线
|
||||
LogMessage?.LogTrace($"Ping ActiveStation {RedundancyOptions.MasterUri} success");
|
||||
await StandbyAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Execute");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private WaitLock _switchLock = new();
|
||||
|
||||
|
||||
|
||||
private bool first;
|
||||
private async Task StandbyAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _switchLock.WaitAsync().ConfigureAwait(false);
|
||||
if (_gatewayRedundantSerivce.StartCollectChannelEnable)
|
||||
{
|
||||
// 输出日志,指示主站已恢复,从站将切换到备用状态
|
||||
if (first)
|
||||
LogMessage?.Warning("Master site has recovered, local machine (standby) will switch to standby state");
|
||||
|
||||
// 将 IsStart 设置为 false,表示当前设备为从站,切换到备用状态
|
||||
_gatewayRedundantSerivce.StartCollectChannelEnable = false;
|
||||
_gatewayRedundantSerivce.StartBusinessChannelEnable = RedundancyOptions?.IsStartBusinessDevice ?? false;
|
||||
await RestartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_switchLock.Release();
|
||||
first = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ActiveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _switchLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
_gatewayRedundantSerivce.StartBusinessChannelEnable = true;
|
||||
if (!_gatewayRedundantSerivce.StartCollectChannelEnable)
|
||||
{
|
||||
// 输出日志,指示无法连接冗余站点,本机将切换到正常状态
|
||||
if (first)
|
||||
LogMessage?.Warning("Cannot connect to redundant site, local machine will switch to normal state");
|
||||
_gatewayRedundantSerivce.StartCollectChannelEnable = true;
|
||||
await RestartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_switchLock.Release();
|
||||
first = true;
|
||||
}
|
||||
}
|
||||
private static Task RestartAsync()
|
||||
{
|
||||
return GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.ReadOnlyIdChannels.Values);
|
||||
}
|
||||
|
||||
public async Task StartTaskAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await StopTaskAsync().ConfigureAwait(false);
|
||||
RedundancyOptions = (await _redundancyService.GetRedundancyAsync().ConfigureAwait(false)).AdaptRedundancyOptions();
|
||||
|
||||
if (RedundancyOptions?.Enable == true)
|
||||
{
|
||||
if (RedundancyOptions.IsMaster)
|
||||
{
|
||||
_tcpDmtpService = await GetTcpDmtpService(RedundancyOptions).ConfigureAwait(false);
|
||||
|
||||
await _tcpDmtpService.StartAsync().ConfigureAwait(false);//启动
|
||||
await ActiveAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tcpDmtpClient = await GetTcpDmtpClient(RedundancyOptions).ConfigureAwait(false);
|
||||
await StandbyAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ActiveAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (RedundancyOptions?.Enable == true)
|
||||
{
|
||||
LogMessage?.LogInformation($"Redundancy task started");
|
||||
if (RedundancyOptions.IsMaster)
|
||||
{
|
||||
scheduledTask = new ScheduledAsyncTask(10, DoMasterWork, null, null, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduledTask = new ScheduledAsyncTask(10, DoSlaveWork, null, null, cancellationToken);
|
||||
}
|
||||
|
||||
scheduledTask.Start();
|
||||
}
|
||||
|
||||
}
|
||||
public async Task StopTaskAsync()
|
||||
{
|
||||
if (scheduledTask?.Enable == true)
|
||||
{
|
||||
LogMessage?.LogInformation($"Redundancy task stoped");
|
||||
scheduledTask?.Stop();
|
||||
}
|
||||
|
||||
if (_tcpDmtpService != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _tcpDmtpService.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
if (_tcpDmtpClient != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _tcpDmtpClient.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await StopTaskAsync().ConfigureAwait(false);
|
||||
TextLogger?.TryDispose();
|
||||
scheduledTask?.TryDispose();
|
||||
|
||||
_tcpDmtpService?.TryDispose();
|
||||
_tcpDmtpClient?.TryDispose();
|
||||
_tcpDmtpService = null;
|
||||
_tcpDmtpClient = null;
|
||||
|
||||
}
|
||||
|
||||
#region
|
||||
|
||||
private async Task<TcpDmtpClient> GetTcpDmtpClient(RedundancyOptions redundancy)
|
||||
{
|
||||
var log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
log?.AddLogger(TextLogger);
|
||||
LogMessage = log;
|
||||
var tcpDmtpClient = new TcpDmtpClient();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetRemoteIPHost(redundancy.MasterUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = redundancy.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||
.SetMaxFailCount(redundancy.MaxErrorCount);
|
||||
});
|
||||
|
||||
await tcpDmtpClient.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpDmtpClient;
|
||||
}
|
||||
|
||||
private async Task<TcpDmtpService> GetTcpDmtpService(RedundancyOptions redundancy)
|
||||
{
|
||||
var log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
log?.AddLogger(TextLogger);
|
||||
LogMessage = log;
|
||||
var tcpDmtpService = new TcpDmtpService();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetListenIPHosts(redundancy.MasterUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = redundancy.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||
.SetMaxFailCount(redundancy.MaxErrorCount);
|
||||
});
|
||||
|
||||
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpDmtpService;
|
||||
}
|
||||
|
||||
private DmtpInvokeOption CreateDmtpInvokeOption(CancellationToken cancellationToken)
|
||||
{
|
||||
return new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
}
|
||||
private RedundancyOptions RedundancyOptions;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region ForcedSync
|
||||
|
||||
WaitLock ForcedSyncWaitLock = new WaitLock();
|
||||
public async Task ForcedSync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await ForcedSyncWaitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
|
||||
if (!RedundancyOptions.IsMaster)
|
||||
return;
|
||||
|
||||
var invokeOption = CreateDmtpInvokeOption(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
await EnsureChannelOpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 如果在线,执行同步
|
||||
bool online = RedundancyOptions.IsServer
|
||||
? _tcpDmtpService.Clients.Count > 0
|
||||
: _tcpDmtpClient.Online;
|
||||
|
||||
if (!online)
|
||||
{
|
||||
LogMessage?.LogWarning("ForcedSync data error, no client online");
|
||||
return;
|
||||
}
|
||||
|
||||
if (RedundancyOptions.IsServer)
|
||||
{
|
||||
foreach (var client in _tcpDmtpService.Clients)
|
||||
{
|
||||
await InvokeSyncDataAsync(client, invokeOption, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await InvokeSyncDataAsync(_tcpDmtpClient, invokeOption, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "ForcedSync data error");
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
ForcedSyncWaitLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InvokeSyncDataAsync(IDmtpActorObject client, DmtpInvokeOption invokeOption, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
await client.GetDmtpRpcActor().InvokeAsync(nameof(ReverseCallbackServer.SyncData), null, invokeOption, GlobalData.Channels.Select(a => a.Value).ToList(), GlobalData.Devices.Select(a => a.Value).ToList(), GlobalData.IdVariables.Select(a => a.Value).ToList())
|
||||
.ConfigureAwait(false);
|
||||
LogMessage?.LogTrace($"ForcedSync data success");
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rpc
|
||||
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataResult = new Dictionary<string, Dictionary<string, OperResult<object>>>();
|
||||
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas = new();
|
||||
foreach (var item in writeInfoLists)
|
||||
{
|
||||
if (deviceDatas.TryGetValue(item.Key.DeviceName ?? string.Empty, out var variableDatas))
|
||||
{
|
||||
variableDatas.TryAdd(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceDatas.TryAdd(item.Key.DeviceName ?? string.Empty, new());
|
||||
deviceDatas[item.Key.DeviceName ?? string.Empty].TryAdd(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
if (RedundancyOptions.IsMaster)
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
|
||||
var invokeOption = CreateDmtpInvokeOption(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
await EnsureChannelOpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
bool online = RedundancyOptions.IsServer ? _tcpDmtpService.Clients.Count > 0 : _tcpDmtpClient.Online;
|
||||
|
||||
if (!online)
|
||||
{
|
||||
LogMessage?.LogWarning("Rpc error, no client online");
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
|
||||
if (RedundancyOptions.IsServer)
|
||||
{
|
||||
await InvokeRpcServerAsync(deviceDatas, dataResult, invokeOption).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
dataResult = await InvokeRpcClientAsync(deviceDatas, invokeOption).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
LogMessage?.LogTrace("Rpc success");
|
||||
return dataResult;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Rpc error");
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, Dictionary<string, OperResult<object>>>> InvokeRpcClientAsync(
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas,
|
||||
DmtpInvokeOption invokeOption)
|
||||
{
|
||||
|
||||
return await _tcpDmtpClient.GetDmtpRpcActor()
|
||||
.InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), invokeOption, deviceDatas)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
private async Task InvokeRpcServerAsync(
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas,
|
||||
Dictionary<string, Dictionary<string, OperResult<object>>> dataResult,
|
||||
DmtpInvokeOption invokeOption)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(item.Key, out var device))
|
||||
{
|
||||
var key = device.Tag;
|
||||
if (_tcpDmtpService.TryGetClient(key, out var client))
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await client.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), invokeOption, new Dictionary<string, Dictionary<string, string>> { { item.Key, item.Value } })
|
||||
.ConfigureAwait(false);
|
||||
|
||||
dataResult.AddRange(data);
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>(ex));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 没找到设备 或 客户端未在线
|
||||
dataResult.TryAdd(item.Key, item.Value.ToDictionary(v => v.Key, v => new OperResult<object>("No online")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Dictionary<string, Dictionary<string, OperResult<object>>> NoOnline(Dictionary<string, Dictionary<string, OperResult<object>>> dataResult, Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
return dataResult;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
private async Task EnsureChannelOpenAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (RedundancyOptions.IsServer)
|
||||
{
|
||||
if (_tcpDmtpService.ServerState != ServerState.Running)
|
||||
{
|
||||
if (_tcpDmtpService.ServerState != ServerState.Stopped)
|
||||
await _tcpDmtpService.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await _tcpDmtpService.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_tcpDmtpClient.Online)
|
||||
await _tcpDmtpClient.TryConnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
@@ -19,10 +19,10 @@ namespace ThingsGateway.Management;
|
||||
|
||||
internal sealed partial class ReverseCallbackServer : SingletonRpcServer
|
||||
{
|
||||
RedundancyHostedService RedundancyHostedService;
|
||||
public ReverseCallbackServer(RedundancyHostedService redundancyHostedService)
|
||||
RedundancyTask RedundancyTask;
|
||||
public ReverseCallbackServer(RedundancyTask redundancyTask)
|
||||
{
|
||||
RedundancyHostedService = redundancyHostedService;
|
||||
RedundancyTask = redundancyTask;
|
||||
}
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
@@ -33,7 +33,7 @@ internal sealed partial class ReverseCallbackServer : SingletonRpcServer
|
||||
{
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(deviceData.Name, out var device))
|
||||
{
|
||||
device.RpcDriver = RedundancyHostedService;
|
||||
device.RpcDriver = RedundancyTask;
|
||||
device.Tag = callContext.Caller is IIdClient idClient ? idClient.Id : string.Empty;
|
||||
|
||||
|
||||
@@ -50,35 +50,106 @@ internal sealed partial class ReverseCallbackServer : SingletonRpcServer
|
||||
|
||||
}
|
||||
}
|
||||
RedundancyHostedService.LogMessage?.Trace("Update data success");
|
||||
RedundancyTask.LogMessage?.Trace("RpcServer Update data success");
|
||||
}
|
||||
|
||||
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public List<DataWithDatabase> GetData()
|
||||
public async Task SyncData(List<Channel> channels, List<Device> devices, List<Variable> variables)
|
||||
{
|
||||
List<DataWithDatabase> dataWithDatabases = new();
|
||||
foreach (var channels in GlobalData.ReadOnlyChannels)
|
||||
|
||||
List<Channel> addChannels = new();
|
||||
List<Device> addDevices = new();
|
||||
List<Variable> addVariables = new();
|
||||
List<Channel> upChannels = new();
|
||||
List<Device> upDevices = new();
|
||||
List<Variable> upVariables = new();
|
||||
|
||||
Dictionary<long, long> channelNewId = new();
|
||||
Dictionary<long, long> deviceNewId = new();
|
||||
|
||||
foreach (var channel in channels)
|
||||
{
|
||||
DataWithDatabase dataWithDatabase = new();
|
||||
dataWithDatabase.Channel = channels.Value;
|
||||
dataWithDatabase.DeviceVariables = new();
|
||||
foreach (var devices in channels.Value.ReadDeviceRuntimes)
|
||||
if (GlobalData.ReadOnlyChannels.TryGetValue(channel.Name, out var channelRuntime))
|
||||
{
|
||||
DeviceDataWithDatabase deviceDataWithDatabase = new();
|
||||
|
||||
deviceDataWithDatabase.Device = devices.Value;
|
||||
deviceDataWithDatabase.Variables = devices.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).Cast<Variable>().ToList();
|
||||
|
||||
|
||||
dataWithDatabase.DeviceVariables.Add(deviceDataWithDatabase);
|
||||
channelNewId.TryAdd(channel.Id, channelRuntime.Id);
|
||||
channel.Id = channelRuntime.Id;
|
||||
channel.Enable = false;
|
||||
upChannels.Add(channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
channelNewId.TryAdd(channel.Id, id);
|
||||
channel.Id = id;
|
||||
channel.Enable = false;
|
||||
addChannels.Add(channel);
|
||||
}
|
||||
|
||||
dataWithDatabases.Add(dataWithDatabase);
|
||||
}
|
||||
return dataWithDatabases;
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(device.Name, out var deviceRuntime))
|
||||
{
|
||||
|
||||
deviceNewId.TryAdd(device.Id, deviceRuntime.Id);
|
||||
device.Id = deviceRuntime.Id;
|
||||
|
||||
channelNewId.TryGetValue(device.ChannelId, out var newid);
|
||||
device.ChannelId = newid;
|
||||
|
||||
device.Enable = false;
|
||||
upDevices.Add(device);
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
deviceNewId.TryAdd(device.Id, id);
|
||||
device.Id = id;
|
||||
|
||||
channelNewId.TryGetValue(device.ChannelId, out var newid);
|
||||
device.ChannelId = newid;
|
||||
device.Enable = false;
|
||||
addDevices.Add(device);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
deviceNewId.TryGetValue(variable.DeviceId, out var newid);
|
||||
if (GlobalData.ReadOnlyIdDevices.TryGetValue(newid, out var deviceRuntime))
|
||||
{
|
||||
if (deviceRuntime.ReadOnlyVariableRuntimes.TryGetValue(variable.Name, out var variableRuntime))
|
||||
{
|
||||
variable.Id = variableRuntime.Id;
|
||||
|
||||
variable.DeviceId = newid;
|
||||
|
||||
upVariables.Add(variable);
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
variable.Id = id;
|
||||
|
||||
variable.DeviceId = newid;
|
||||
addVariables.Add(variable);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
variable.Id = id;
|
||||
|
||||
variable.DeviceId = newid;
|
||||
addVariables.Add(variable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await GlobalData.ChannelRuntimeService.InsertAsync(addChannels, addDevices, addVariables, true, default).ConfigureAwait(false);
|
||||
await GlobalData.ChannelRuntimeService.UpdateAsync(upChannels, upDevices, upVariables, true, default).ConfigureAwait(false);
|
||||
|
||||
RedundancyTask.LogMessage?.LogTrace($"Sync data success");
|
||||
|
||||
}
|
||||
|
||||
|
@@ -129,7 +129,7 @@ internal sealed class UpdateZipFileHostedService : BackgroundService, IUpdateZip
|
||||
{
|
||||
var upgradeServerOptions = App.GetOptions<UpgradeServerOptions>();
|
||||
if (!upgradeServerOptions.Enable)
|
||||
throw new Exception("未启用更新服务");
|
||||
throw new Exception("Update service not enabled");
|
||||
|
||||
//设置调用配置
|
||||
var tokenSource = new CancellationTokenSource();//可取消令箭源,可用于取消Rpc的调用
|
||||
@@ -156,16 +156,19 @@ internal sealed class UpdateZipFileHostedService : BackgroundService, IUpdateZip
|
||||
}
|
||||
|
||||
private readonly WaitLock WaitLock = new();
|
||||
private readonly WaitLock UpdateWaitLock = new();
|
||||
public async Task Update(UpdateZipFile updateZipFile, Func<Task<bool>> check = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UpdateWaitLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
var upgradeServerOptions = App.GetOptions<UpgradeServerOptions>();
|
||||
if (!upgradeServerOptions.Enable)
|
||||
return;
|
||||
if (WaitLock.Waited)
|
||||
{
|
||||
_log.LogWarning("正在更新中,请稍后再试");
|
||||
_log.LogWarning("Updating, please try again later");
|
||||
return;
|
||||
}
|
||||
try
|
||||
@@ -198,6 +201,10 @@ internal sealed class UpdateZipFileHostedService : BackgroundService, IUpdateZip
|
||||
_log.LogWarning(ex);
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
UpdateWaitLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -197,82 +197,8 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
|
||||
#region worker服务
|
||||
|
||||
private async Task StartAll(CancellationToken cancellationToken)
|
||||
{
|
||||
Clear();
|
||||
|
||||
Rules = await App.GetService<IRulesService>().GetAllAsync().ConfigureAwait(false);
|
||||
Diagrams = new();
|
||||
foreach (var rules in Rules.Where(a => a.Status))
|
||||
{
|
||||
var item = Init(rules);
|
||||
Start(item.rulesLog, item.blazorDiagram, cancellationToken);
|
||||
}
|
||||
dispatchService.Dispatch(null);
|
||||
|
||||
_ = Task.Factory.StartNew(async (state) =>
|
||||
{
|
||||
if (state is not Dictionary<RulesLog, Diagram> diagrams)
|
||||
{
|
||||
return;
|
||||
}
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
foreach (var item in diagrams?.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);
|
||||
}
|
||||
}, Diagrams, 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(ThingsGateway.Gateway.Application.AppResource.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)
|
||||
@@ -295,18 +221,65 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
||||
Diagrams.Clear();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return StartAsync();
|
||||
await Task.Yield();
|
||||
|
||||
try
|
||||
{
|
||||
await RestartLock.WaitAsync(cancellationToken).ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||||
TokenSource ??= new CancellationTokenSource();
|
||||
Clear();
|
||||
|
||||
Rules = await App.GetService<IRulesService>().GetAllAsync().ConfigureAwait(false);
|
||||
Diagrams = new();
|
||||
foreach (var rules in Rules.Where(a => a.Status))
|
||||
{
|
||||
var item = Init(rules);
|
||||
Start(item.rulesLog, item.blazorDiagram, TokenSource.Token);
|
||||
}
|
||||
dispatchService.Dispatch(null);
|
||||
|
||||
_logger.LogInformation(ThingsGateway.Gateway.Application.AppResource.RulesEngineTaskStart);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Start"); // 记录错误日志
|
||||
}
|
||||
finally
|
||||
{
|
||||
RestartLock.Release(); // 释放锁
|
||||
}
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
foreach (var item in Diagrams?.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);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return StopAsync();
|
||||
try
|
||||
{
|
||||
await RestartLock.WaitAsync(cancellationToken).ConfigureAwait(false); // 等待获取锁,以确保只有一个线程可以执行以下代码
|
||||
Cancel();
|
||||
Clear();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Stop"); // 记录错误日志
|
||||
}
|
||||
finally
|
||||
{
|
||||
RestartLock.Release(); // 释放锁
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -15,6 +15,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
@@ -59,7 +60,7 @@ internal static class RuntimeServiceHelper
|
||||
//批量修改之后,需要重新加载通道
|
||||
foreach (var newChannelRuntime in newChannelRuntimes)
|
||||
{
|
||||
if (GlobalData.Channels.TryGetValue(newChannelRuntime.Id, out var channelRuntime))
|
||||
if (GlobalData.IdChannels.TryGetValue(newChannelRuntime.Id, out var channelRuntime))
|
||||
{
|
||||
channelRuntime.Dispose();
|
||||
newChannelRuntime.Init();
|
||||
@@ -85,7 +86,7 @@ internal static class RuntimeServiceHelper
|
||||
{
|
||||
|
||||
|
||||
if (GlobalData.Channels.TryGetValue(newDeviceRuntime.ChannelId, out var newChannelRuntime))
|
||||
if (GlobalData.IdChannels.TryGetValue(newDeviceRuntime.ChannelId, out var newChannelRuntime))
|
||||
{
|
||||
newDeviceRuntime.Init(newChannelRuntime);
|
||||
|
||||
@@ -121,7 +122,7 @@ internal static class RuntimeServiceHelper
|
||||
{
|
||||
deviceRuntime.Dispose();
|
||||
}
|
||||
if (GlobalData.Channels.TryGetValue(newDeviceRuntime.ChannelId, out var channelRuntime))
|
||||
if (GlobalData.IdChannels.TryGetValue(newDeviceRuntime.ChannelId, out var channelRuntime))
|
||||
{
|
||||
newDeviceRuntime.Init(channelRuntime);
|
||||
}
|
||||
@@ -214,7 +215,7 @@ internal static class RuntimeServiceHelper
|
||||
//批量修改之后,需要重新加载通道
|
||||
foreach (var id in ids)
|
||||
{
|
||||
if (GlobalData.Channels.TryGetValue(id, out var channelRuntime))
|
||||
if (GlobalData.IdChannels.TryGetValue(id, out var channelRuntime))
|
||||
{
|
||||
channelRuntime.Dispose();
|
||||
var devs = channelRuntime.DeviceRuntimes.Select(a => a.Value).ToArray();
|
||||
@@ -267,7 +268,7 @@ internal static class RuntimeServiceHelper
|
||||
public static async Task RemoveDeviceAsync(HashSet<long> newDeciceIds)
|
||||
{
|
||||
//先找出线程管理器,停止
|
||||
var deviceRuntimes = GlobalData.IdDevices.Where(a => newDeciceIds.Contains(a.Key)).Select(a => a.Value);
|
||||
var deviceRuntimes = GlobalData.IdDevices.FilterByKeys(newDeciceIds).Select(a => a.Value);
|
||||
await RemoveDeviceAsync(deviceRuntimes).ConfigureAwait(false);
|
||||
}
|
||||
public static async Task RemoveDeviceAsync(IEnumerable<DeviceRuntime> deviceRuntimes)
|
||||
@@ -317,7 +318,7 @@ internal static class RuntimeServiceHelper
|
||||
|
||||
public static void AddBusinessChangedDriver(HashSet<long> variableIds, ConcurrentHashSet<IDriver> changedDriver)
|
||||
{
|
||||
var data = GlobalData.IdVariables.Where(a => variableIds.Contains(a.Key)).GroupBy(a => a.Value.DeviceRuntime);
|
||||
var data = GlobalData.IdVariables.FilterByKeys(variableIds).GroupBy(a => a.Value.DeviceRuntime);
|
||||
|
||||
foreach (var group in data)
|
||||
{
|
||||
|
@@ -13,7 +13,6 @@ using BootstrapBlazor.Components;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
|
@@ -57,8 +57,9 @@ public partial class ChannelTable : IDisposable
|
||||
{
|
||||
scheduler.Trigger();
|
||||
}
|
||||
private async Task Notify()
|
||||
private async Task Notify(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
if (Disposed) return;
|
||||
if (table != null)
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
|
@@ -1313,8 +1313,9 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
}
|
||||
|
||||
|
||||
private async Task Notify()
|
||||
private async Task Notify(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
if (Disposed) return;
|
||||
await OnClickSearch(SearchText);
|
||||
|
||||
@@ -1339,7 +1340,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
case ChannelDevicePluginTypeEnum.Channel:
|
||||
return new ChannelDeviceTreeItem()
|
||||
{
|
||||
ChannelRuntime = GlobalData.ReadOnlyChannels.TryGetValue(channelDeviceTreeItem.ChannelRuntime?.Id ?? 0, out var channel) ? channel : channelDeviceTreeItem.ChannelRuntime,
|
||||
ChannelRuntime = GlobalData.ReadOnlyIdChannels.TryGetValue(channelDeviceTreeItem.ChannelRuntime?.Id ?? 0, out var channel) ? channel : channelDeviceTreeItem.ChannelRuntime,
|
||||
ChannelDevicePluginType = ChannelDevicePluginTypeEnum.Channel
|
||||
};
|
||||
|
||||
|
@@ -61,7 +61,7 @@
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-md-6 ">
|
||||
<BootstrapInputGroup>
|
||||
<Select IsVirtualize DefaultVirtualizeItemText=@(GlobalData.ReadOnlyChannels.TryGetValue(value.ChannelId,out var channelRuntime)?channelRuntime.Name:string.Empty) @bind-Value="@value.ChannelId" IsDisabled=BatchEditEnable Items="@_channelItems" OnSelectedItemChanged=OnChannelChanged ShowSearch="true" ShowLabel="true" />
|
||||
<Select IsVirtualize DefaultVirtualizeItemText=@(GlobalData.ReadOnlyIdChannels.TryGetValue(value.ChannelId,out var channelRuntime)?channelRuntime.Name:string.Empty) @bind-Value="@value.ChannelId" IsDisabled=BatchEditEnable Items="@_channelItems" OnSelectedItemChanged=OnChannelChanged ShowSearch="true" ShowLabel="true" />
|
||||
<Button IsDisabled=BatchEditEnable class="text-end" Icon="fa-solid fa-plus" OnClick="AddChannel"></Button>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
|
@@ -111,7 +111,7 @@ public partial class DeviceEditComponent
|
||||
};
|
||||
|
||||
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
|
||||
var pluginName = GlobalData.ReadOnlyChannels.TryGetValue(device.ChannelId, out var channel) ? channel.PluginName : string.Empty;
|
||||
var pluginName = GlobalData.ReadOnlyIdChannels.TryGetValue(device.ChannelId, out var channel) ? channel.PluginName : string.Empty;
|
||||
var items = new List<SelectedItem>() { new SelectedItem(string.Empty, "none") }.Concat(devices.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(option.SearchText))
|
||||
.Where(a => a.PluginName == pluginName && a.Id != device.Id).Take(20).BuildDeviceSelectList()
|
||||
);
|
||||
@@ -128,7 +128,7 @@ public partial class DeviceEditComponent
|
||||
{
|
||||
try
|
||||
{
|
||||
var pluginName = GlobalData.ReadOnlyChannels.TryGetValue(selectedItem.Value.ToLong(), out var channel) ? channel.PluginName : string.Empty;
|
||||
var pluginName = GlobalData.ReadOnlyIdChannels.TryGetValue(selectedItem.Value.ToLong(), out var channel) ? channel.PluginName : string.Empty;
|
||||
if (pluginName.IsNullOrEmpty()) return;
|
||||
var data = GlobalData.PluginService.GetDriverPropertyTypes(pluginName);
|
||||
Model.ModelValueValidateForm = new ModelValueValidateForm() { Value = data.Model };
|
||||
|
@@ -56,8 +56,9 @@ public partial class DeviceTable : IDisposable
|
||||
{
|
||||
scheduler.Trigger();
|
||||
}
|
||||
private async Task Notify()
|
||||
private async Task Notify(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
if (Disposed) return;
|
||||
if (table != null)
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
|
@@ -15,7 +15,7 @@ else if (ShowType == ShowTypeEnum.LogInfo)
|
||||
{
|
||||
<DeviceRuntimeInfo DeviceRuntime="device" />
|
||||
}
|
||||
if (GlobalData.ReadOnlyChannels.TryGetValue(ShowChannelRuntime, out var channel))
|
||||
if (GlobalData.ReadOnlyIdChannels.TryGetValue(ShowChannelRuntime, out var channel))
|
||||
{
|
||||
<ChannelRuntimeInfo ChannelRuntime="channel" />
|
||||
}
|
||||
|
@@ -84,8 +84,9 @@ public partial class VariableRuntimeInfo : IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Notify()
|
||||
private async Task Notify(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
if (Disposed) return;
|
||||
if (table != null)
|
||||
await InvokeAsync(table.QueryAsync);
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
<EditComponent ItemsPerRow=1 Model="Model" OnSave="OnSaveRedundancy" />
|
||||
|
||||
<Button IsDisabled=@(Model.IsMaster||(!Model.Enable)) OnClick="ForcedSync">@RedundancyLocalizer["ForcedSync"]</Button>
|
||||
<Button IsDisabled=@(!Model.IsMaster||(!Model.Enable)) OnClick="ForcedSync">@RedundancyLocalizer["ForcedSync"]</Button>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 h-100">
|
||||
|
||||
|
@@ -74,12 +74,10 @@ public partial class RedundancyOptionsPage
|
||||
await RedundancyService.EditRedundancyOptionAsync(Model);
|
||||
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Save"]}{RazorLocalizer["Success"]}");
|
||||
|
||||
await RedundancyHostedService.StopRedundancyTaskAsync();
|
||||
var result = await RedundancyHostedService.StartRedundancyTaskAsync();
|
||||
if (result.IsSuccess)
|
||||
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Success"]}");
|
||||
else
|
||||
await ToastService.Warning(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Fail", result.ToString()]}");
|
||||
await RedundancyHostedService.StopTaskAsync();
|
||||
await RedundancyHostedService.StartTaskAsync(CancellationToken.None);
|
||||
await ToastService.Success(RedundancyLocalizer[nameof(RedundancyOptions)], $"{RazorLocalizer["Success"]}");
|
||||
|
||||
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
30
src/Plugin/ThingsGateway.Plugin.DB/Mapper/GatewayMapper.cs
Normal file
30
src/Plugin/ThingsGateway.Plugin.DB/Mapper/GatewayMapper.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Plugin.SqlHistoryAlarm;
|
||||
|
||||
namespace ThingsGateway.Plugin.DB;
|
||||
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
|
||||
public static partial class GatewayMapper
|
||||
{
|
||||
public static partial List<HistoryAlarm> AdaptListHistoryAlarm(this IEnumerable<AlarmVariable> src);
|
||||
|
||||
[MapProperty(nameof(AlarmVariable.Id), nameof(HistoryAlarm.Id), Use = nameof(MapId))]
|
||||
private static partial HistoryAlarm AdaptHistoryAlarm(AlarmVariable src);
|
||||
|
||||
[UserMapping(Default = false)]
|
||||
private static long MapId(long id) => CommonUtils.GetSingleId();
|
||||
|
||||
}
|
||||
|
||||
|
@@ -23,8 +23,10 @@ namespace ThingsGateway.Plugin.QuestDB;
|
||||
/// <summary>
|
||||
/// QuestDBProducer
|
||||
/// </summary>
|
||||
public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableModel<VariableBasicData>, IDBHistoryValueService
|
||||
public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable, IDBHistoryValueService
|
||||
{
|
||||
|
||||
|
||||
internal readonly RealDBProducerProperty _driverPropertys = new();
|
||||
private readonly QuestDBProducerVariableProperty _variablePropertys = new();
|
||||
|
||||
@@ -45,6 +47,9 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
|
||||
public override VariablePropertyBase VariablePropertys => _variablePropertys;
|
||||
|
||||
protected override BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval => _driverPropertys;
|
||||
|
||||
|
||||
|
||||
private SqlSugarClient _db;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -53,17 +58,6 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public async Task<SqlSugarPagedList<IDBHistoryValue>> GetDBHistoryValuePagesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
var data = await Query(input).ToPagedListAsync<QuestDBNumberHistoryValue, IDBHistoryValue>(input.Current, input.Size).ConfigureAwait(false);//分页
|
||||
return data;
|
||||
}
|
||||
|
||||
public async Task<List<IDBHistoryValue>> GetDBHistoryValuesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
var data = await Query(input).ToListAsync().ConfigureAwait(false);
|
||||
return data.Cast<IDBHistoryValue>().ToList();
|
||||
}
|
||||
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -81,6 +75,40 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
|
||||
return $" {nameof(QuestDBProducer)}";
|
||||
}
|
||||
|
||||
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
DynamicSQLBase? hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
hisModel.Logger = LogMessage;
|
||||
await hisModel.DBInit(_db, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_db.CodeFirst.As<QuestDBNumberHistoryValue>(_driverPropertys.NumberTableName).InitTables(typeof(QuestDBNumberHistoryValue));
|
||||
_db.CodeFirst.As<QuestDBHistoryValue>(_driverPropertys.StringTableName).InitTables(typeof(QuestDBHistoryValue));
|
||||
}
|
||||
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
public async Task<SqlSugarPagedList<IDBHistoryValue>> GetDBHistoryValuePagesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
var data = await Query(input).ToPagedListAsync<QuestDBNumberHistoryValue, IDBHistoryValue>(input.Current, input.Size).ConfigureAwait(false);//分页
|
||||
return data;
|
||||
}
|
||||
|
||||
public async Task<List<IDBHistoryValue>> GetDBHistoryValuesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
var data = await Query(input).ToListAsync().ConfigureAwait(false);
|
||||
return data.Cast<IDBHistoryValue>().ToList();
|
||||
}
|
||||
internal ISugarQueryable<QuestDBNumberHistoryValue> Query(DBHistoryValuePageInput input)
|
||||
{
|
||||
var db = BusinessDatabaseUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
|
||||
@@ -142,36 +170,4 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
DynamicSQLBase? hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
hisModel.Logger = LogMessage;
|
||||
await hisModel.DBInit(_db, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_db.CodeFirst.As<QuestDBNumberHistoryValue>(_driverPropertys.NumberTableName).InitTables(typeof(QuestDBNumberHistoryValue));
|
||||
_db.CodeFirst.As<QuestDBHistoryValue>(_driverPropertys.StringTableName).InitTables(typeof(QuestDBHistoryValue));
|
||||
}
|
||||
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
await UpdateVarModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsMemory(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await UpdateVarModelCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsCache(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ namespace ThingsGateway.Plugin.QuestDB;
|
||||
/// <summary>
|
||||
/// RabbitMQProducer
|
||||
/// </summary>
|
||||
public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableModel<VariableBasicData>
|
||||
public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
{
|
||||
|
||||
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableBasicData>> item, CancellationToken cancellationToken)
|
||||
|
@@ -12,6 +12,7 @@ using BootstrapBlazor.Components;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Debug;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||
@@ -24,8 +25,12 @@ namespace ThingsGateway.Plugin.SqlDB;
|
||||
/// <summary>
|
||||
/// SqlDBProducer
|
||||
/// </summary>
|
||||
public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<VariableBasicData>, IDBHistoryValueService
|
||||
public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable, IDBHistoryValueService
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
internal readonly SqlDBProducerProperty _driverPropertys = new();
|
||||
private readonly SqlDBProducerVariableProperty _variablePropertys = new();
|
||||
|
||||
@@ -73,6 +78,123 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
|
||||
return $" {nameof(SqlDBProducer)}";
|
||||
}
|
||||
|
||||
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
_db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys);
|
||||
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum == BusinessUpdateEnum.Interval && _driverPropertys.IsReadDB)
|
||||
{
|
||||
GlobalData.VariableValueChangeEvent += VariableValueChange;
|
||||
}
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RealTimeVariables.Clear();
|
||||
_initRealData = false;
|
||||
return base.AfterVariablesChangedAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
await hisModel.DBInit(_db, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
_db.CodeFirst.InitTables(typeof(SQLHistoryValue));
|
||||
_db.CodeFirst.InitTables(typeof(SQLNumberHistoryValue));
|
||||
|
||||
}
|
||||
}
|
||||
if (!_driverPropertys.BigTextScriptRealTable.IsNullOrEmpty())
|
||||
{
|
||||
var realModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptRealTable);
|
||||
|
||||
if (_driverPropertys.IsReadDB)
|
||||
{
|
||||
await realModel.DBInit(_db, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_driverPropertys.IsReadDB)
|
||||
_db.CodeFirst.As<SQLRealValue>(_driverPropertys.ReadDBTableName).InitTables<SQLRealValue>();
|
||||
}
|
||||
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_driverPropertys.IsReadDB)
|
||||
{
|
||||
try
|
||||
{
|
||||
var varLists = RealTimeVariables.ToIEnumerableWithDequeue().Batch(100000);
|
||||
foreach (var varList in varLists)
|
||||
{
|
||||
var result = await UpdateAsync(varList, cancellationToken).ConfigureAwait(false);
|
||||
if (success != result.IsSuccess)
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
LogMessage?.LogWarning(result.ToString());
|
||||
success = result.IsSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
await Update(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private ISugarQueryable<SQLNumberHistoryValue> Query(DBHistoryValuePageInput input)
|
||||
{
|
||||
var db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys);
|
||||
|
||||
var query = db.Queryable<SQLNumberHistoryValue>().SplitTable()
|
||||
.WhereIF(input.StartTime != null, a => a.CreateTime >= input.StartTime)
|
||||
.WhereIF(input.EndTime != null, a => a.CreateTime <= input.EndTime)
|
||||
.WhereIF(!string.IsNullOrEmpty(input.VariableName), it => it.Name.Contains(input.VariableName))
|
||||
.WhereIF(input.VariableNames != null, it => input.VariableNames.Contains(it.Name))
|
||||
;
|
||||
|
||||
for (int i = input.SortField.Count - 1; i >= 0; i--)
|
||||
{
|
||||
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
|
||||
}
|
||||
query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
internal async Task<QueryData<SQLNumberHistoryValue>> QueryHistoryData(QueryPageOptions option)
|
||||
{
|
||||
var db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys);
|
||||
@@ -154,121 +276,4 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
_db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys);
|
||||
|
||||
if (_businessPropertyWithCacheInterval.BusinessUpdateEnum == BusinessUpdateEnum.Interval && _driverPropertys.IsReadDB)
|
||||
{
|
||||
GlobalData.VariableValueChangeEvent += VariableValueChange;
|
||||
}
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RealTimeVariables.Clear();
|
||||
_initRealData = false;
|
||||
return base.AfterVariablesChangedAsync(cancellationToken);
|
||||
}
|
||||
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
await hisModel.DBInit(_db, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
_db.CodeFirst.InitTables(typeof(SQLHistoryValue));
|
||||
_db.CodeFirst.InitTables(typeof(SQLNumberHistoryValue));
|
||||
|
||||
}
|
||||
}
|
||||
if (!_driverPropertys.BigTextScriptRealTable.IsNullOrEmpty())
|
||||
{
|
||||
var realModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptRealTable);
|
||||
|
||||
if (_driverPropertys.IsReadDB)
|
||||
{
|
||||
await realModel.DBInit(_db, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_driverPropertys.IsReadDB)
|
||||
_db.CodeFirst.As<SQLRealValue>(_driverPropertys.ReadDBTableName).InitTables<SQLRealValue>();
|
||||
}
|
||||
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_driverPropertys.IsReadDB)
|
||||
{
|
||||
try
|
||||
{
|
||||
var varList = RealTimeVariables.ToListWithDequeue();
|
||||
if (varList.Count > 0)
|
||||
{
|
||||
var result = await UpdateAsync(varList, cancellationToken).ConfigureAwait(false);
|
||||
if (success != result.IsSuccess)
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
LogMessage?.LogWarning(result.ToString());
|
||||
success = result.IsSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
{
|
||||
await UpdateVarModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsCache(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private ISugarQueryable<SQLNumberHistoryValue> Query(DBHistoryValuePageInput input)
|
||||
{
|
||||
var db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys);
|
||||
|
||||
var query = db.Queryable<SQLNumberHistoryValue>().SplitTable()
|
||||
.WhereIF(input.StartTime != null, a => a.CreateTime >= input.StartTime)
|
||||
.WhereIF(input.EndTime != null, a => a.CreateTime <= input.EndTime)
|
||||
.WhereIF(!string.IsNullOrEmpty(input.VariableName), it => it.Name.Contains(input.VariableName))
|
||||
.WhereIF(input.VariableNames != null, it => input.VariableNames.Contains(it.Name))
|
||||
;
|
||||
|
||||
for (int i = input.SortField.Count - 1; i >= 0; i--)
|
||||
{
|
||||
query = query.OrderByIF(!string.IsNullOrEmpty(input.SortField[i]), $"{input.SortField[i]} {(input.SortDesc[i] ? "desc" : "asc")}");
|
||||
}
|
||||
query = query.OrderBy(it => it.Id, OrderByType.Desc);//排序
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ namespace ThingsGateway.Plugin.SqlDB;
|
||||
/// <summary>
|
||||
/// SqlDBProducer
|
||||
/// </summary>
|
||||
public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<VariableBasicData>
|
||||
public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
{
|
||||
private volatile bool _initRealData;
|
||||
private ConcurrentDictionary<long, VariableBasicData> RealTimeVariables { get; } = new ConcurrentDictionary<long, VariableBasicData>();
|
||||
@@ -77,7 +77,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
|
||||
|
||||
private void UpdateVariable(VariableRuntime variableRuntime, VariableBasicData variable)
|
||||
{
|
||||
if (_driverPropertys.IsHistoryDB)
|
||||
if (_driverPropertys.IsHistoryDB && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
{
|
||||
if (_driverPropertys.GroupUpdate && variable.BusinessGroupUpdateTrigger && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
@@ -200,7 +200,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
|
||||
stopwatch.Start();
|
||||
var ids = (await _db.Queryable<SQLRealValue>().AS(_driverPropertys.ReadDBTableName).Select(a => a.Id).ToListAsync(cancellationToken).ConfigureAwait(false)).ToHashSet();
|
||||
var InsertData = IdVariableRuntimes.Where(a => !ids.Contains(a.Key)).Select(a => a.Value).AdaptListSQLRealValue();
|
||||
var result = await _db.Fastest<SQLRealValue>().AS(_driverPropertys.ReadDBTableName).PageSize(100000).BulkCopyAsync(InsertData).ConfigureAwait(false);
|
||||
var result = await _db.Fastest<SQLRealValue>().AS(_driverPropertys.ReadDBTableName).BulkCopyAsync(InsertData).ConfigureAwait(false);
|
||||
_initRealData = true;
|
||||
stopwatch.Stop();
|
||||
if (result > 0)
|
||||
|
@@ -11,20 +11,18 @@
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.SqlSugar;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Plugin.SqlHistoryAlarm;
|
||||
|
||||
/// <summary>
|
||||
/// SqlHistoryAlarm
|
||||
/// </summary>
|
||||
public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<HistoryAlarm>, IDBHistoryAlarmService
|
||||
public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm, IDBHistoryAlarmService
|
||||
{
|
||||
|
||||
internal readonly SqlHistoryAlarmProperty _driverPropertys = new();
|
||||
private readonly SqlHistoryAlarmVariableProperty _variablePropertys = new();
|
||||
public override Type DriverUIType => typeof(HistoryAlarmPage);
|
||||
@@ -34,66 +32,14 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<Histor
|
||||
|
||||
protected override BusinessPropertyWithCache _businessPropertyWithCache => _driverPropertys;
|
||||
|
||||
|
||||
private SqlSugarClient _db;
|
||||
|
||||
public static HistoryAlarm AdaptHistoryAlarm(AlarmVariable src)
|
||||
{
|
||||
var target = new HistoryAlarm();
|
||||
target.Name = src.Name;
|
||||
target.Description = src.Description;
|
||||
target.CreateOrgId = src.CreateOrgId;
|
||||
target.CreateUserId = src.CreateUserId;
|
||||
target.DeviceId = src.DeviceId;
|
||||
target.DeviceName = src.DeviceName;
|
||||
target.RegisterAddress = src.RegisterAddress;
|
||||
target.DataType = src.DataType;
|
||||
target.AlarmCode = src.AlarmCode;
|
||||
target.AlarmLimit = src.AlarmLimit;
|
||||
target.AlarmText = src.AlarmText;
|
||||
target.RecoveryCode = src.RecoveryCode;
|
||||
target.AlarmTime = src.AlarmTime;
|
||||
target.EventTime = src.EventTime;
|
||||
target.AlarmType = src.AlarmType;
|
||||
target.EventType = src.EventType;
|
||||
target.Remark1 = src.Remark1;
|
||||
target.Remark2 = src.Remark2;
|
||||
target.Remark3 = src.Remark3;
|
||||
target.Remark4 = src.Remark4;
|
||||
target.Remark5 = src.Remark5;
|
||||
target.Id = CommonUtils.GetSingleId();
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
_db = BusinessDatabaseUtil.GetDb((DbType)_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
|
||||
|
||||
GlobalData.AlarmChangedEvent -= AlarmWorker_OnAlarmChanged;
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
|
||||
{
|
||||
AlarmWorker_OnAlarmChanged(a.Value);
|
||||
});
|
||||
GlobalData.AlarmChangedEvent += AlarmWorker_OnAlarmChanged;
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.ReadOnlyIdVariables.Where(a => a.Value.AlarmEnable));
|
||||
|
||||
|
||||
var ids = IdVariableRuntimes.Select(b => b.Value.DeviceId).ToHashSet();
|
||||
|
||||
CollectDevices = GlobalData.ReadOnlyIdDevices
|
||||
.Where(a => IdVariableRuntimes.Select(b => b.Value.DeviceId).Contains(a.Value.Id))
|
||||
.ToDictionary(a => a.Key, a => a.Value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
@@ -104,7 +50,6 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<Histor
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_db?.TryDispose();
|
||||
GlobalData.AlarmChangedEvent -= AlarmWorker_OnAlarmChanged;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
@@ -115,11 +60,6 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<Histor
|
||||
return base.ProtectedStartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
return Update(cancellationToken);
|
||||
}
|
||||
|
||||
#region 数据查询
|
||||
|
||||
public async Task<SqlSugarPagedList<IDBHistoryAlarm>> GetDBHistoryAlarmPagesAsync(DBHistoryAlarmPageInput input)
|
||||
|
@@ -20,41 +20,19 @@ namespace ThingsGateway.Plugin.SqlHistoryAlarm;
|
||||
/// <summary>
|
||||
/// SqlHistoryAlarm
|
||||
/// </summary>
|
||||
public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<HistoryAlarm>
|
||||
public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm
|
||||
{
|
||||
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<HistoryAlarm>> item, CancellationToken cancellationToken)
|
||||
|
||||
protected override void AlarmChange(AlarmVariable alarmVariable)
|
||||
{
|
||||
return UpdateT(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
|
||||
AddQueueAlarmModel(new CacheDBItem<AlarmVariable>(alarmVariable));
|
||||
}
|
||||
|
||||
protected override ValueTask<OperResult> UpdateVarModels(IEnumerable<HistoryAlarm> item, CancellationToken cancellationToken)
|
||||
protected override ValueTask<OperResult> UpdateAlarmModel(IEnumerable<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken)
|
||||
{
|
||||
return UpdateT(item, cancellationToken);
|
||||
return UpdateAlarmModel(item.Select(a => a.Value).OrderBy(a => a.Id), cancellationToken);
|
||||
}
|
||||
|
||||
private void AlarmWorker_OnAlarmChanged(AlarmVariable alarmVariable)
|
||||
{
|
||||
if (CurrentDevice.Pause)
|
||||
return;
|
||||
AddQueueVarModel(new CacheDBItem<HistoryAlarm>(AdaptHistoryAlarm(alarmVariable)));
|
||||
}
|
||||
public override void PauseThread(bool pause)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var oldV = CurrentDevice.Pause;
|
||||
base.PauseThread(pause);
|
||||
if (!pause && oldV != pause)
|
||||
{
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a =>
|
||||
{
|
||||
AlarmWorker_OnAlarmChanged(a.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<OperResult> UpdateT(IEnumerable<HistoryAlarm> item, CancellationToken cancellationToken)
|
||||
private async ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await InserableAsync(item.ToList(), cancellationToken).ConfigureAwait(false);
|
||||
if (success != result.IsSuccess)
|
||||
@@ -67,7 +45,7 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<Histor
|
||||
return result;
|
||||
}
|
||||
|
||||
private async ValueTask<OperResult> InserableAsync(List<HistoryAlarm> dbInserts, CancellationToken cancellationToken)
|
||||
private async ValueTask<OperResult> InserableAsync(List<AlarmVariable> dbInserts, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -91,7 +69,7 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheVariableModel<Histor
|
||||
if (_db.CurrentConnectionConfig.DbType == SqlSugar.DbType.QuestDB)
|
||||
result = await _db.Insertable(dbInserts).AS(_driverPropertys.TableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
else
|
||||
result = await _db.Fastest<HistoryAlarm>().AS(_driverPropertys.TableName).PageSize(50000).BulkCopyAsync(dbInserts).ConfigureAwait(false);
|
||||
result = await _db.Fastest<HistoryAlarm>().AS(_driverPropertys.TableName).PageSize(50000).BulkCopyAsync(dbInserts.AdaptListHistoryAlarm()).ConfigureAwait(false);
|
||||
|
||||
stopwatch.Stop();
|
||||
//var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false);
|
||||
|
@@ -25,8 +25,11 @@ namespace ThingsGateway.Plugin.TDengineDB;
|
||||
/// <summary>
|
||||
/// TDengineDBProducer
|
||||
/// </summary>
|
||||
public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableModel<VariableBasicData>, IDBHistoryValueService
|
||||
public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable, IDBHistoryValueService
|
||||
{
|
||||
|
||||
|
||||
|
||||
internal readonly RealDBProducerProperty _driverPropertys = new()
|
||||
{
|
||||
DbType = DbType.TDengine,
|
||||
@@ -51,17 +54,7 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableM
|
||||
|
||||
protected override BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval => _driverPropertys;
|
||||
|
||||
public async Task<SqlSugarPagedList<IDBHistoryValue>> GetDBHistoryValuePagesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
var data = await Query(input, _driverPropertys.NumberTableNameLow).ToPagedListAsync<TDengineDBNumberHistoryValue, IDBHistoryValue>(input.Current, input.Size).ConfigureAwait(false);//分页
|
||||
return data;
|
||||
}
|
||||
|
||||
public async Task<List<IDBHistoryValue>> GetDBHistoryValuesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
var data = await Query(input, _driverPropertys.NumberTableNameLow).ToListAsync().ConfigureAwait(false);
|
||||
return data.Cast<IDBHistoryValue>().ToList(); ;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
@@ -97,6 +90,60 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableM
|
||||
return $" {nameof(TDengineDBProducer)}";
|
||||
}
|
||||
|
||||
private SqlSugarClient _db;
|
||||
|
||||
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
{
|
||||
await hisModel.DBInit(_db, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var sql = $"""
|
||||
CREATE STABLE IF NOT EXISTS `{_driverPropertys.StringTableNameLow}`(
|
||||
`createtime` TIMESTAMP ,
|
||||
`collecttime` TIMESTAMP ,
|
||||
`id` BIGINT ,
|
||||
`isonline` BOOL ,
|
||||
`value` VARCHAR(255) ) TAGS(`devicename` VARCHAR(100) ,`name` VARCHAR(100))
|
||||
""";
|
||||
await _db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
sql = $"""
|
||||
CREATE STABLE IF NOT EXISTS `{_driverPropertys.NumberTableNameLow}`(
|
||||
`createtime` TIMESTAMP ,
|
||||
`collecttime` TIMESTAMP ,
|
||||
`id` BIGINT ,
|
||||
`isonline` BOOL ,
|
||||
`value` DOUBLE ) TAGS(`devicename` VARCHAR(100) ,`name` VARCHAR(100))
|
||||
""";
|
||||
await _db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<SqlSugarPagedList<IDBHistoryValue>> GetDBHistoryValuePagesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
var data = await Query(input, _driverPropertys.NumberTableNameLow).ToPagedListAsync<TDengineDBNumberHistoryValue, IDBHistoryValue>(input.Current, input.Size).ConfigureAwait(false);//分页
|
||||
return data;
|
||||
}
|
||||
|
||||
public async Task<List<IDBHistoryValue>> GetDBHistoryValuesAsync(DBHistoryValuePageInput input)
|
||||
{
|
||||
var data = await Query(input, _driverPropertys.NumberTableNameLow).ToListAsync().ConfigureAwait(false);
|
||||
return data.Cast<IDBHistoryValue>().ToList(); ;
|
||||
}
|
||||
internal ISugarQueryable<TDengineDBNumberHistoryValue> Query(DBHistoryValuePageInput input, string tableName)
|
||||
{
|
||||
var db = TDengineDBUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr, tableName);
|
||||
@@ -156,56 +203,4 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableM
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
private SqlSugarClient _db;
|
||||
|
||||
protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_db.DbMaintenance.CreateDatabase();
|
||||
|
||||
|
||||
//必须为间隔上传
|
||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||
{
|
||||
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
||||
{
|
||||
await hisModel.DBInit(_db, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var sql = $"""
|
||||
CREATE STABLE IF NOT EXISTS `{_driverPropertys.StringTableNameLow}`(
|
||||
`createtime` TIMESTAMP ,
|
||||
`collecttime` TIMESTAMP ,
|
||||
`id` BIGINT ,
|
||||
`isonline` BOOL ,
|
||||
`value` VARCHAR(255) ) TAGS(`devicename` VARCHAR(100) ,`name` VARCHAR(100))
|
||||
""";
|
||||
await _db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
sql = $"""
|
||||
CREATE STABLE IF NOT EXISTS `{_driverPropertys.NumberTableNameLow}`(
|
||||
`createtime` TIMESTAMP ,
|
||||
`collecttime` TIMESTAMP ,
|
||||
`id` BIGINT ,
|
||||
`isonline` BOOL ,
|
||||
`value` DOUBLE ) TAGS(`devicename` VARCHAR(100) ,`name` VARCHAR(100))
|
||||
""";
|
||||
await _db.Ado.ExecuteCommandAsync(sql, default, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
await UpdateVarModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsMemory(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelCache(cancellationToken).ConfigureAwait(false);
|
||||
await UpdateVarModelsCache(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ namespace ThingsGateway.Plugin.TDengineDB;
|
||||
/// <summary>
|
||||
/// RabbitMQProducer
|
||||
/// </summary>
|
||||
public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariableModel<VariableBasicData>
|
||||
public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable
|
||||
{
|
||||
|
||||
protected override ValueTask<OperResult> UpdateVarModel(IEnumerable<CacheDBItem<VariableBasicData>> item, CancellationToken cancellationToken)
|
||||
|
@@ -10,6 +10,8 @@
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
|
||||
<!--<PackageReference Include="SqlSugar.QuestDb.RestAPI" Version="4.1.17" GeneratePathProperty="true">
|
||||
<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@@ -13,7 +13,7 @@ namespace ThingsGateway.Plugin.Webhook;
|
||||
/// <summary>
|
||||
/// WebhookClient,RPC方法适配mqttNet
|
||||
/// </summary>
|
||||
public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class Webhook : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private readonly WebhookProperty _driverPropertys = new();
|
||||
private readonly WebhookVariableProperty _variablePropertys = new();
|
||||
@@ -23,10 +23,4 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
|
||||
/// <inheritdoc/>
|
||||
public override bool IsConnected() => success;
|
||||
|
||||
protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
return Update(cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ namespace ThingsGateway.Plugin.Webhook;
|
||||
/// <summary>
|
||||
/// WebhookClient
|
||||
/// </summary>
|
||||
public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class Webhook : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
|
||||
protected override void AlarmChange(AlarmVariable alarmVariable)
|
||||
@@ -116,6 +116,7 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
|
||||
{
|
||||
if (!_businessPropertyWithCacheIntervalScript.VariableTopic.IsNullOrWhiteSpace())
|
||||
{
|
||||
|
||||
if (_driverPropertys.GroupUpdate && variable.BusinessGroupUpdateTrigger && !variable.BusinessGroup.IsNullOrEmpty() && VariableRuntimeGroups.TryGetValue(variable.BusinessGroup, out var variableRuntimeGroup))
|
||||
{
|
||||
|
||||
@@ -177,7 +178,7 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScript<VariableBasic
|
||||
|
||||
#region private
|
||||
|
||||
private async ValueTask<OperResult> Update(List<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var topicArray in topicArrayList)
|
||||
{
|
||||
|
@@ -19,7 +19,7 @@ namespace ThingsGateway.Plugin.Kafka;
|
||||
/// <summary>
|
||||
/// Kafka消息生产
|
||||
/// </summary>
|
||||
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private readonly KafkaProducerProperty _driverPropertys = new();
|
||||
private readonly KafkaProducerVariableProperty _variablePropertys = new();
|
||||
|
@@ -21,7 +21,7 @@ namespace ThingsGateway.Plugin.Kafka;
|
||||
/// <summary>
|
||||
/// Kafka消息生产
|
||||
/// </summary>
|
||||
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class KafkaProducer : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private IProducer<Null, byte[]> _producer;
|
||||
private ProducerBuilder<Null, byte[]> _producerBuilder;
|
||||
@@ -89,7 +89,7 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(group.AdaptIEnumerableVariableBasicData()));
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(group.ToList()));
|
||||
}
|
||||
foreach (var variable in varList)
|
||||
{
|
||||
@@ -126,7 +126,7 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
|
||||
|
||||
#region private
|
||||
|
||||
private async ValueTask<OperResult> Update(List<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var topicArray in topicArrayList)
|
||||
{
|
||||
@@ -174,7 +174,7 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScript<Variabl
|
||||
//保留消息
|
||||
//分解List,避免超出字节大小限制
|
||||
var varData = IdVariableRuntimes.Select(a => a.Value).AdaptIEnumerableVariableBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var devData = CollectDevices?.Select(a => a.Value).AdaptListDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var devData = CollectDevices?.Select(a => a.Value).AdaptIEnumerableDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).ChunkBetter(_driverPropertys.SplitSize);
|
||||
foreach (var item in varData)
|
||||
{
|
||||
|
@@ -172,7 +172,7 @@ public class ModbusSlave : BusinessBase
|
||||
await Task.Delay(10000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
var list = ModbusVariableQueue.ToDictWithDequeue();
|
||||
var list = ModbusVariableQueue.ToIEnumerableKVWithDequeue();
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
@@ -246,7 +246,7 @@ public class ModbusSlave : BusinessBase
|
||||
/// <inheritdoc/>
|
||||
private void VariableValueChange(VariableRuntime variableRuntime, VariableBasicData variableData)
|
||||
{
|
||||
if (CurrentDevice.Pause == true)
|
||||
if (CurrentDevice?.Pause != false)
|
||||
return;
|
||||
var address = variableRuntime.GetPropertyValue(DeviceId, nameof(_variablePropertys.ServiceAddress));
|
||||
if (address != null && variableRuntime.Value != null)
|
||||
|
@@ -26,7 +26,7 @@ namespace ThingsGateway.Plugin.Mqtt;
|
||||
/// <summary>
|
||||
/// MqttClient,RPC方法适配mqttNet
|
||||
/// </summary>
|
||||
public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private readonly MqttClientProperty _driverPropertys = new();
|
||||
private readonly MqttClientVariableProperty _variablePropertys = new();
|
||||
@@ -206,12 +206,9 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
//TD设备上线
|
||||
|
||||
var data = ThingsBoardDeviceConnectQueue.ToListWithDequeue();
|
||||
if (data?.Count > 0)
|
||||
foreach (var item in data)
|
||||
{
|
||||
foreach (var item in data)
|
||||
{
|
||||
await UpdateThingsBoardDeviceConnect(item).ConfigureAwait(false);
|
||||
}
|
||||
await UpdateThingsBoardDeviceConnect(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -37,7 +37,7 @@ namespace ThingsGateway.Plugin.Mqtt;
|
||||
/// <summary>
|
||||
/// MqttClient
|
||||
/// </summary>
|
||||
public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private static readonly CompositeFormat RpcTopic = CompositeFormat.Parse("{0}/+");
|
||||
public const string ThingsBoardRpcTopic = "v1/gateway/rpc";
|
||||
@@ -148,7 +148,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(group.AdaptIEnumerableVariableBasicData()));
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(group.ToList()));
|
||||
}
|
||||
foreach (var variable in varList)
|
||||
{
|
||||
@@ -206,7 +206,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
|
||||
#region private
|
||||
|
||||
private async ValueTask<OperResult> Update(List<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (TopicArray topicArray in topicArrayList)
|
||||
{
|
||||
@@ -255,7 +255,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
//保留消息
|
||||
//分解List,避免超出mqtt字节大小限制
|
||||
var varData = IdVariableRuntimes.Select(a => a.Value).AdaptIEnumerableVariableBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var devData = CollectDevices?.Select(a => a.Value).AdaptListDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var devData = CollectDevices?.Select(a => a.Value).AdaptIEnumerableDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).ChunkBetter(_driverPropertys.SplitSize);
|
||||
foreach (var item in varData)
|
||||
{
|
||||
|
@@ -23,7 +23,7 @@ namespace ThingsGateway.Plugin.Mqtt;
|
||||
/// <summary>
|
||||
/// MqttServer,RPC方法适配mqttNet
|
||||
/// </summary>
|
||||
public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private readonly MqttServerProperty _driverPropertys = new();
|
||||
private readonly MqttClientVariableProperty _variablePropertys = new();
|
||||
|
@@ -34,7 +34,7 @@ namespace ThingsGateway.Plugin.Mqtt;
|
||||
/// <summary>
|
||||
/// MqttServer
|
||||
/// </summary>
|
||||
public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private static readonly CompositeFormat RpcTopic = CompositeFormat.Parse("{0}/+");
|
||||
private MQTTnet.Server.MqttServer _mqttServer;
|
||||
@@ -103,7 +103,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(group.AdaptIEnumerableVariableBasicData()));
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(group.ToList()));
|
||||
}
|
||||
foreach (var variable in varList)
|
||||
{
|
||||
@@ -138,7 +138,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
}
|
||||
#region private
|
||||
|
||||
private async ValueTask<OperResult> Update(List<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var topicArray in topicArrayList)
|
||||
{
|
||||
@@ -250,7 +250,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScript<VariableBa
|
||||
//首次连接时的保留消息
|
||||
//分解List,避免超出mqtt字节大小限制
|
||||
var varData = IdVariableRuntimes.Select(a => a.Value).AdaptIEnumerableVariableBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var devData = CollectDevices?.Select(a => a.Value).AdaptListDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var devData = CollectDevices?.Select(a => a.Value).AdaptIEnumerableDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).ChunkBetter(_driverPropertys.SplitSize);
|
||||
List<MqttApplicationMessage> Messages = new();
|
||||
|
||||
|
@@ -64,7 +64,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
ConcurrentList<IDriver>? dbDrivers;
|
||||
internal FolderState rootFolder;
|
||||
|
||||
public void RefreshVariable()
|
||||
private void RefreshVariable()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
@@ -90,14 +90,17 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
foreach (var item in _geviceGroup)
|
||||
{
|
||||
//设备树会有两层
|
||||
FolderState fs = CreateFolder(rootFolder, item.Key, item.Key);
|
||||
FolderState fs = CreateFolder(rootFolder, item.FirstOrDefault().DeviceRuntime);
|
||||
fs.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
|
||||
fs.EventNotifier = EventNotifiers.SubscribeToEvents;
|
||||
if (item?.Count() > 0)
|
||||
if (item?.Any() == true)
|
||||
{
|
||||
FolderState varFolder = CreateFolder(fs, "Variables", "Variables");
|
||||
varFolder.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
|
||||
varFolder.EventNotifier = EventNotifiers.SubscribeToEvents;
|
||||
foreach (var item2 in item)
|
||||
{
|
||||
CreateVariable(fs, item2);
|
||||
CreateVariable(varFolder, item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,6 +371,111 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
return folder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建文件夹
|
||||
/// </summary>
|
||||
private FolderState CreateFolder(NodeState parent, DeviceRuntime deviceRuntime)
|
||||
{
|
||||
if (deviceRuntime != null)
|
||||
{
|
||||
var name = deviceRuntime.Name;
|
||||
var description = deviceRuntime.Description;
|
||||
FolderState folder = new(parent)
|
||||
{
|
||||
SymbolicName = name,
|
||||
ReferenceTypeId = ReferenceTypes.Organizes,
|
||||
TypeDefinitionId = ObjectTypeIds.FolderType,
|
||||
Description = description,
|
||||
NodeId = new NodeId(name, NamespaceIndex),
|
||||
BrowseName = new QualifiedName(name, NamespaceIndex),
|
||||
DisplayName = new LocalizedText(name),
|
||||
WriteMask = AttributeWriteMask.None,
|
||||
UserWriteMask = AttributeWriteMask.None,
|
||||
EventNotifier = EventNotifiers.None
|
||||
};
|
||||
|
||||
// 添加自定义属性
|
||||
{
|
||||
var property = new PropertyState<string>(folder)
|
||||
{
|
||||
NodeId = new NodeId($"{deviceRuntime.Name}.PluginName", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("PluginName", NamespaceIndex),
|
||||
DisplayName = "PluginName",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = deviceRuntime.PluginName ?? string.Empty
|
||||
};
|
||||
AddProperty(folder, property);
|
||||
}
|
||||
{
|
||||
var property = new PropertyState<string>(folder)
|
||||
{
|
||||
NodeId = new NodeId($"{deviceRuntime.Name}.Remark1", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark1", NamespaceIndex),
|
||||
DisplayName = "Remark1",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = deviceRuntime.Remark1 ?? string.Empty
|
||||
};
|
||||
AddProperty(folder, property);
|
||||
}
|
||||
{
|
||||
var property = new PropertyState<string>(folder)
|
||||
{
|
||||
NodeId = new NodeId($"{deviceRuntime.Name}.Remark2", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark2", NamespaceIndex),
|
||||
DisplayName = "Remark2",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = deviceRuntime.Remark2 ?? string.Empty
|
||||
};
|
||||
AddProperty(folder, property);
|
||||
}
|
||||
{
|
||||
var property = new PropertyState<string>(folder)
|
||||
{
|
||||
NodeId = new NodeId($"{deviceRuntime.Name}.Remark3", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark3", NamespaceIndex),
|
||||
DisplayName = "Remark3",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = deviceRuntime.Remark3 ?? string.Empty
|
||||
};
|
||||
AddProperty(folder, property);
|
||||
}
|
||||
{
|
||||
var property = new PropertyState<string>(folder)
|
||||
{
|
||||
NodeId = new NodeId($"{deviceRuntime.Name}.Remark4", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark4", NamespaceIndex),
|
||||
DisplayName = "Remark4",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = deviceRuntime.Remark4 ?? string.Empty
|
||||
};
|
||||
AddProperty(folder, property);
|
||||
}
|
||||
{
|
||||
var property = new PropertyState<string>(folder)
|
||||
{
|
||||
NodeId = new NodeId($"{deviceRuntime.Name}.Remark5", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark5", NamespaceIndex),
|
||||
DisplayName = "Remark5",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = deviceRuntime.Remark5 ?? string.Empty
|
||||
};
|
||||
AddProperty(folder, property);
|
||||
}
|
||||
|
||||
|
||||
parent?.AddChild(folder);
|
||||
|
||||
return folder;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个值节点,类型需要在创建的时候指定
|
||||
/// </summary>
|
||||
@@ -386,7 +494,7 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Id = variableRuntime.Id,
|
||||
DataType = DataNodeType(variableRuntime)
|
||||
DataType = DataNodeType(variableRuntime),
|
||||
};
|
||||
var service = dbDrivers.FirstOrDefault(a => GlobalData.ContainsVariable(a.DeviceId, variableRuntime));
|
||||
var level = ThingsGatewayNodeManager.ProtectTypeTrans(variableRuntime, service != null);
|
||||
@@ -400,9 +508,111 @@ public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
variable.Timestamp = variableRuntime.CollectTime;
|
||||
variable.OnWriteValue = OnWriteDataValue;
|
||||
parent?.AddChild(variable);
|
||||
|
||||
// 添加自定义属性
|
||||
{
|
||||
var property = new PropertyState<string>(variable)
|
||||
{
|
||||
NodeId = new NodeId($"{variableRuntime.DeviceName}.{variableRuntime.Name}.Unit", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Unit", NamespaceIndex),
|
||||
DisplayName = "Unit",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = variableRuntime.Unit ?? string.Empty
|
||||
};
|
||||
AddProperty(variable, property);
|
||||
|
||||
}
|
||||
if (!variableRuntime.CollectGroup.IsNullOrEmpty())
|
||||
{
|
||||
|
||||
var property = new PropertyState<string>(variable)
|
||||
{
|
||||
NodeId = new NodeId($"{variableRuntime.DeviceName}.{variableRuntime.Name}.CollectGroup", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("CollectGroup", NamespaceIndex),
|
||||
DisplayName = "CollectGroup",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = variableRuntime.CollectGroup,
|
||||
};
|
||||
AddProperty(variable, property);
|
||||
}
|
||||
{
|
||||
var property = new PropertyState<string>(variable)
|
||||
{
|
||||
NodeId = new NodeId($"{variableRuntime.DeviceName}.{variableRuntime.Name}.Remark1", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark1", NamespaceIndex),
|
||||
DisplayName = "Remark1",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = variableRuntime.Remark1 ?? string.Empty
|
||||
};
|
||||
AddProperty(variable, property);
|
||||
}
|
||||
|
||||
{
|
||||
var property = new PropertyState<string>(variable)
|
||||
{
|
||||
NodeId = new NodeId($"{variableRuntime.DeviceName}.{variableRuntime.Name}.Remark2", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark2", NamespaceIndex),
|
||||
DisplayName = "Remark2",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = variableRuntime.Remark2 ?? string.Empty
|
||||
};
|
||||
AddProperty(variable, property);
|
||||
}
|
||||
|
||||
{
|
||||
var property = new PropertyState<string>(variable)
|
||||
{
|
||||
NodeId = new NodeId($"{variableRuntime.DeviceName}.{variableRuntime.Name}.Remark3", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark3", NamespaceIndex),
|
||||
DisplayName = "Remark3",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = variableRuntime.Remark3 ?? string.Empty
|
||||
};
|
||||
AddProperty(variable, property);
|
||||
}
|
||||
|
||||
{
|
||||
var property = new PropertyState<string>(variable)
|
||||
{
|
||||
NodeId = new NodeId($"{variableRuntime.DeviceName}.{variableRuntime.Name}.Remark4", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark4", NamespaceIndex),
|
||||
DisplayName = "Remark4",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = variableRuntime.Remark4 ?? string.Empty
|
||||
};
|
||||
AddProperty(variable, property);
|
||||
}
|
||||
{
|
||||
var property = new PropertyState<string>(variable)
|
||||
{
|
||||
NodeId = new NodeId($"{variableRuntime.DeviceName}.{variableRuntime.Name}.Remark5", NamespaceIndex),
|
||||
BrowseName = new QualifiedName("Remark5", NamespaceIndex),
|
||||
DisplayName = "Remark5",
|
||||
DataType = DataTypeIds.String,
|
||||
ValueRank = ValueRanks.Scalar,
|
||||
Value = variableRuntime.Remark5 ?? string.Empty
|
||||
};
|
||||
AddProperty(variable, property);
|
||||
}
|
||||
|
||||
NodeIdTags.AddOrUpdate($"{variableRuntime.DeviceName}.{variableRuntime.Name}", variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
|
||||
public void AddProperty(BaseInstanceState parent, BaseInstanceState property)
|
||||
{
|
||||
parent.AddReference(ReferenceTypeIds.HasProperty, false, property.NodeId);
|
||||
property.AddReference(ReferenceTypeIds.HasProperty, true, parent.NodeId);
|
||||
AddPredefinedNode(SystemContext, property);
|
||||
}
|
||||
|
||||
#region 多写
|
||||
public override void Write(OperationContext context, IList<WriteValue> nodesToWrite, IList<ServiceResult> errors)
|
||||
{
|
||||
|
@@ -16,7 +16,6 @@ using Opc.Ua.Configuration;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||
@@ -176,32 +175,26 @@ public partial class OpcUaServer : BusinessBase
|
||||
await Task.Delay(10000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
var varList = CollectVariableRuntimes.ToListWithDequeue();
|
||||
if (varList.Count > 0)
|
||||
var varList = CollectVariableRuntimes.ToIEnumerableWithDequeue();
|
||||
foreach (var item in varList)
|
||||
{
|
||||
if (varList?.Count > 0)
|
||||
try
|
||||
{
|
||||
foreach (var item in varList)
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
m_server?.NodeManager?.UpVariable(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
m_server?.NodeManager?.UpVariable(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
|
@@ -19,7 +19,7 @@ namespace ThingsGateway.Plugin.RabbitMQ;
|
||||
/// <summary>
|
||||
/// RabbitMQProducer
|
||||
/// </summary>
|
||||
public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private readonly RabbitMQProducerProperty _driverPropertys = new();
|
||||
private readonly RabbitMQProducerVariableProperty _variablePropertys = new();
|
||||
|
@@ -23,7 +23,7 @@ namespace ThingsGateway.Plugin.RabbitMQ;
|
||||
/// <summary>
|
||||
/// RabbitMQProducer
|
||||
/// </summary>
|
||||
public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<VariableBasicData, DeviceBasicData, AlarmVariable>
|
||||
public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScriptAll
|
||||
{
|
||||
private IConnection _connection;
|
||||
private ConnectionFactory _connectionFactory;
|
||||
@@ -90,7 +90,7 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
|
||||
|
||||
foreach (var group in varGroup)
|
||||
{
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(group.AdaptIEnumerableVariableBasicData()));
|
||||
AddQueueVarModel(new CacheDBItem<List<VariableBasicData>>(group.ToList()));
|
||||
}
|
||||
foreach (var variable in varList)
|
||||
{
|
||||
@@ -127,7 +127,7 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
|
||||
|
||||
#region private
|
||||
|
||||
private async ValueTask<OperResult> Update(List<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var topicArray in topicArrayList)
|
||||
{
|
||||
@@ -176,7 +176,7 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScript<Vari
|
||||
//保留消息
|
||||
//分解List,避免超出字节大小限制
|
||||
var varData = IdVariableRuntimes.Select(a => a.Value).AdaptIEnumerableVariableBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var devData = CollectDevices?.Select(a => a.Value).AdaptListDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var devData = CollectDevices?.Select(a => a.Value).AdaptIEnumerableDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize);
|
||||
var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).ChunkBetter(_driverPropertys.SplitSize);
|
||||
foreach (var item in varData)
|
||||
{
|
||||
|
@@ -1,18 +0,0 @@
|
||||
<Project>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--在构建后触发的。它通过在 Nuget 包的 Content 文件夹中包含目标目录中的所有文件和子文件夹来创建 nuget 包-->
|
||||
<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Synchronization*.dll">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>Content</PackagePath>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
@@ -1,16 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
global using ThingsGateway.Gateway.Application;
|
@@ -1,14 +0,0 @@
|
||||
{
|
||||
|
||||
"ThingsGateway.Plugin.Synchronization.SynchronizationProperty": {
|
||||
"IsServer": "IsServer",
|
||||
"IsMaster": "IsMaster",
|
||||
"IsAllVariable": "IsAllVariable",
|
||||
"ServerUri": "ServerUri",
|
||||
"VerifyToken": "VerifyToken",
|
||||
"HeartbeatInterval": "HeartbeatInterval",
|
||||
"SyncInterval": "SyncInterval"
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
{
|
||||
|
||||
"ThingsGateway.Plugin.Synchronization.SynchronizationProperty": {
|
||||
"IsServer": "是否Tcp服务",
|
||||
"IsMaster": "是否主站",
|
||||
"IsAllVariable": "选择全部变量",
|
||||
"ServerUri": "服务Uri",
|
||||
"VerifyToken": "验证令牌",
|
||||
"HeartbeatInterval": "心跳间隔",
|
||||
"SyncInterval": "同步间隔"
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
public class DeviceDataWithValue
|
||||
{
|
||||
/// <inheritdoc cref="Device.Name"/>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.ActiveTime"/>
|
||||
public DateTime ActiveTime { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.DeviceStatus"/>
|
||||
public DeviceStatusEnum DeviceStatus { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.LastErrorMessage"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string LastErrorMessage { get; set; }
|
||||
|
||||
/// <inheritdoc cref="DeviceRuntime.ReadOnlyVariableRuntimes"/>
|
||||
public Dictionary<string, VariableDataWithValue> ReadOnlyVariableRuntimes { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class DataWithDatabase
|
||||
{
|
||||
public Channel Channel { get; set; }
|
||||
public List<DeviceDataWithDatabase> DeviceVariables { get; set; }
|
||||
}
|
||||
|
||||
public class DeviceDataWithDatabase
|
||||
{
|
||||
public Device Device { get; set; }
|
||||
public List<Variable> Variables { get; set; }
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
public partial class ReverseCallbackServer : SingletonRpcServer
|
||||
{
|
||||
Synchronization Synchronization;
|
||||
public ReverseCallbackServer(Synchronization synchronization)
|
||||
{
|
||||
Synchronization = synchronization;
|
||||
}
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public void UpData(ICallContext callContext, List<DeviceDataWithValue> deviceDatas)
|
||||
{
|
||||
foreach (var deviceData in deviceDatas)
|
||||
{
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(deviceData.Name, out var device))
|
||||
{
|
||||
device.RpcDriver = Synchronization;
|
||||
device.Tag = callContext.Caller is IIdClient idClient ? idClient.Id : string.Empty;
|
||||
|
||||
device.SetDeviceStatus(deviceData.ActiveTime, deviceData.DeviceStatus == DeviceStatusEnum.OnLine ? false : true, lastErrorMessage: deviceData.LastErrorMessage);
|
||||
|
||||
foreach (var variableData in deviceData.ReadOnlyVariableRuntimes)
|
||||
{
|
||||
if (device.ReadOnlyVariableRuntimes.TryGetValue(variableData.Key, out var value))
|
||||
{
|
||||
value.SetValue(variableData.Value.RawValue, variableData.Value.CollectTime, variableData.Value.IsOnline);
|
||||
value.SetErrorMessage(variableData.Value.LastErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Synchronization.LogMessage?.Trace("Update data success");
|
||||
}
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public List<DataWithDatabase> GetData()
|
||||
{
|
||||
List<DataWithDatabase> dataWithDatabases = new();
|
||||
foreach (var device in Synchronization.CollectDevices)
|
||||
{
|
||||
DataWithDatabase dataWithDatabase = new();
|
||||
dataWithDatabase.Channel = device.Value.ChannelRuntime;
|
||||
dataWithDatabase.DeviceVariables = new();
|
||||
DeviceDataWithDatabase deviceDataWithDatabase = new();
|
||||
|
||||
deviceDataWithDatabase.Device = device.Value;
|
||||
|
||||
deviceDataWithDatabase.Variables = device.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).Cast<Variable>().ToList();
|
||||
|
||||
dataWithDatabase.DeviceVariables.Add(deviceDataWithDatabase);
|
||||
|
||||
dataWithDatabases.Add(dataWithDatabase);
|
||||
}
|
||||
return dataWithDatabases;
|
||||
}
|
||||
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public Task<Dictionary<string, Dictionary<string, IOperResult>>> Rpc(Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken)
|
||||
{
|
||||
return GlobalData.RpcService.InvokeDeviceMethodAsync(Synchronization.DeviceName, deviceDatas, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,478 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Extension.Generic;
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
|
||||
public partial class Synchronization : BusinessBase, IRpcDriver
|
||||
{
|
||||
public override VariablePropertyBase VariablePropertys => new SynchronizationVariableProperty();
|
||||
internal SynchronizationProperty _driverPropertys = new();
|
||||
protected override BusinessPropertyBase _businessPropertyBase => _driverPropertys;
|
||||
|
||||
public override Type DriverUIType => typeof(SynchronizationRuntimeRazor);
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
|
||||
if (_driverPropertys.IsAllVariable)
|
||||
{
|
||||
LogMessage?.LogInformation("Refresh variable");
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override bool IsConnected()
|
||||
{
|
||||
return _driverPropertys.IsServer ? _tcpDmtpService?.ServerState == ServerState.Running : _tcpDmtpClient?.Online == true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_driverPropertys.IsServer)
|
||||
{
|
||||
if (_tcpDmtpService.ServerState != ServerState.Running)
|
||||
{
|
||||
if (_tcpDmtpService.ServerState != ServerState.Stopped)
|
||||
{
|
||||
await _tcpDmtpService.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await _tcpDmtpService.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (_driverPropertys.IsMaster)
|
||||
{
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
if (_tcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
var deviceRunTimes = CollectDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).AdaptDeviceDataWithValue();
|
||||
|
||||
foreach (var item in _tcpDmtpService.Clients)
|
||||
{
|
||||
if (item.Online)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
await item.GetDmtpRpcActor().InvokeAsync(
|
||||
nameof(ReverseCallbackServer.UpData), null, waitInvoke, deviceRunTimes).ConfigureAwait(false);
|
||||
LogMessage?.LogTrace($"{item.GetIPPort()} Update data success");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
bool online = true;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
if (!_tcpDmtpClient.Online)
|
||||
online = (await _tcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
if (_driverPropertys.IsMaster)
|
||||
{
|
||||
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
var deviceRunTimes = CollectDevices.Where(a => a.Value.IsCollect == true).Select(a => a.Value).AdaptDeviceDataWithValue();
|
||||
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
await _tcpDmtpClient.GetDmtpRpcActor().InvokeAsync(
|
||||
nameof(ReverseCallbackServer.UpData), null, waitInvoke, deviceRunTimes).ConfigureAwait(false);
|
||||
LogMessage?.LogTrace($"Update data success");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
private TcpDmtpClient? _tcpDmtpClient;
|
||||
private TcpDmtpService? _tcpDmtpService;
|
||||
protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_driverPropertys.IsServer)
|
||||
{
|
||||
_tcpDmtpService = await GetTcpDmtpService().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tcpDmtpClient = await GetTcpDmtpClient().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_tcpDmtpClient?.SafeDispose();
|
||||
_tcpDmtpService?.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private async Task<TcpDmtpClient> GetTcpDmtpClient()
|
||||
{
|
||||
var tcpDmtpClient = new TcpDmtpClient();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetRemoteIPHost(_driverPropertys.ServerUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = _driverPropertys.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(_driverPropertys.HeartbeatInterval))
|
||||
.SetMaxFailCount(3);
|
||||
});
|
||||
|
||||
await tcpDmtpClient.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpDmtpClient;
|
||||
}
|
||||
|
||||
private async Task<TcpDmtpService> GetTcpDmtpService()
|
||||
{
|
||||
var tcpDmtpService = new TcpDmtpService();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetListenIPHosts(_driverPropertys.ServerUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetDmtpOption(new DmtpOption() { VerifyToken = _driverPropertys.VerifyToken })
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer(new ReverseCallbackServer(this));
|
||||
});
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(_driverPropertys.HeartbeatInterval))
|
||||
.SetMaxFailCount(3);
|
||||
});
|
||||
|
||||
await tcpDmtpService.SetupAsync(config).ConfigureAwait(false);
|
||||
return tcpDmtpService;
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask ForcedSync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_driverPropertys.IsMaster)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (!_driverPropertys.IsServer)
|
||||
{
|
||||
online = (await _tcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
var data = await _tcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(
|
||||
nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false);
|
||||
|
||||
await Add(data, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_tcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
foreach (var item in _tcpDmtpService.Clients)
|
||||
{
|
||||
var data = await item.GetDmtpRpcActor().InvokeTAsync<List<DataWithDatabase>>(nameof(ReverseCallbackServer.GetData), waitInvoke).ConfigureAwait(false);
|
||||
|
||||
|
||||
await Add(data, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage?.LogWarning("ForcedSync data error, no client online");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "ForcedSync data error");
|
||||
}
|
||||
|
||||
}
|
||||
private async Task Add(List<DataWithDatabase> data, CancellationToken cancellationToken)
|
||||
{
|
||||
data.ForEach(a =>
|
||||
{
|
||||
a.Channel.Enable = false;
|
||||
a.Channel.Id = CommonUtils.GetSingleId();
|
||||
a.DeviceVariables.ForEach(b =>
|
||||
{
|
||||
b.Device.ChannelId = a.Channel.Id;
|
||||
b.Device.Id = CommonUtils.GetSingleId();
|
||||
b.Variables.ForEach(c =>
|
||||
{
|
||||
c.DeviceId = b.Device.Id;
|
||||
c.Id = 0;
|
||||
});
|
||||
});
|
||||
});
|
||||
await GlobalData.ChannelRuntimeService.CopyAsync(data.Select(a => a.Channel).ToList(), data.SelectMany(a => a.DeviceVariables).ToDictionary(a => a.Device, a => a.Variables), true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogTrace($"ForcedSync data success");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
Dictionary<string, Dictionary<string, OperResult<object>>> dataResult = new();
|
||||
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas = new();
|
||||
foreach (var item in writeInfoLists)
|
||||
{
|
||||
if (deviceDatas.TryGetValue(item.Key.DeviceName ?? string.Empty, out var variableDatas))
|
||||
{
|
||||
variableDatas.TryAdd(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceDatas.TryAdd(item.Key.DeviceName ?? string.Empty, new());
|
||||
deviceDatas[item.Key.DeviceName ?? string.Empty].TryAdd(item.Key.Name, item.Value?.ToString() ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
if (_driverPropertys.IsMaster)
|
||||
{
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
var waitInvoke = new DmtpInvokeOption()
|
||||
{
|
||||
FeedbackType = FeedbackType.WaitInvoke,
|
||||
Token = cancellationToken,
|
||||
Timeout = 30000,
|
||||
SerializationType = SerializationType.Json,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (!_driverPropertys.IsServer)
|
||||
{
|
||||
online = (await _tcpDmtpClient.TryConnectAsync().ConfigureAwait(false)).ResultCode == ResultCode.Success;
|
||||
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
dataResult = await _tcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), waitInvoke, deviceDatas).ConfigureAwait(false);
|
||||
|
||||
LogMessage?.LogTrace($"Rpc success");
|
||||
|
||||
return dataResult;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_tcpDmtpService.Clients.Count != 0)
|
||||
{
|
||||
online = true;
|
||||
}
|
||||
// 如果 online 为 true,表示设备在线
|
||||
if (online)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
|
||||
if (GlobalData.ReadOnlyDevices.TryGetValue(item.Key, out var device))
|
||||
{
|
||||
var key = device.Tag;
|
||||
|
||||
if (_tcpDmtpService.TryGetClient(key, out var client))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var data = await client.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(ReverseCallbackServer.Rpc), waitInvoke, new Dictionary<string, Dictionary<string, string>>() { { item.Key, item.Value } }).ConfigureAwait(false);
|
||||
|
||||
dataResult.AddRange(data);
|
||||
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
|
||||
LogMessage?.LogTrace($"Rpc success");
|
||||
return dataResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage?.LogWarning("Rpc error, no client online");
|
||||
}
|
||||
}
|
||||
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 输出警告日志,指示同步数据到从站时发生错误
|
||||
LogMessage?.LogWarning(ex, "Rpc error");
|
||||
return NoOnline(dataResult, deviceDatas);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, Dictionary<string, OperResult<object>>> NoOnline(Dictionary<string, Dictionary<string, OperResult<object>>> dataResult, Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
{
|
||||
foreach (var item in deviceDatas)
|
||||
{
|
||||
dataResult.TryAdd(item.Key, new Dictionary<string, OperResult<object>>());
|
||||
|
||||
foreach (var vItem in item.Value)
|
||||
{
|
||||
dataResult[item.Key].TryAdd(vItem.Key, new OperResult<object>("No online"));
|
||||
}
|
||||
}
|
||||
return dataResult;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 异步写入方法
|
||||
/// </summary>
|
||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||
/// <param name="cancellationToken">取消操作的通知</param>
|
||||
/// <returns>写入操作的结果字典</returns>
|
||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await Rpc(writeInfoLists, cancellationToken).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (IOperResult)b.Value));
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
public partial class Synchronization : BusinessBase
|
||||
{
|
||||
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class SynchronizationProperty : BusinessPropertyBase, IBusinessPropertyAllVariableBase
|
||||
{
|
||||
[DynamicProperty]
|
||||
public bool IsServer { get; set; } = true;
|
||||
[DynamicProperty]
|
||||
public bool IsMaster { get; set; }
|
||||
|
||||
[DynamicProperty]
|
||||
public bool IsAllVariable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置远程 URI,用于通信。
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public string ServerUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置用于验证的令牌。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DynamicProperty]
|
||||
public string VerifyToken { get; set; } = "ThingsGateway";
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置心跳间隔。
|
||||
/// </summary>
|
||||
[MinValue(3000)]
|
||||
[DynamicProperty]
|
||||
public int HeartbeatInterval { get; set; } = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置冗余数据同步间隔(ms)。
|
||||
/// </summary>
|
||||
[MinValue(1000)]
|
||||
[DynamicProperty]
|
||||
public int SyncInterval { get; set; } = 3000;
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
@using BootstrapBlazor.Components
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using ThingsGateway.Extension
|
||||
@using ThingsGateway.Foundation
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@namespace ThingsGateway.Plugin.Synchronization
|
||||
|
||||
|
||||
|
||||
<Button IsDisabled=Synchronization._driverPropertys.IsMaster IsAsync class="mx-2" Color=Color.Primary OnClick="ForcedSync">ForcedSync</Button>
|
||||
|
||||
|
||||
|
@@ -1,44 +0,0 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
#pragma warning disable CA2007 // 考虑对等待的任务调用 ConfigureAwait
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization
|
||||
{
|
||||
public partial class SynchronizationRuntimeRazor : IDriverUIBase
|
||||
{
|
||||
[Parameter, EditorRequired]
|
||||
public object Driver { get; set; }
|
||||
public Synchronization Synchronization => Driver as Synchronization;
|
||||
[Inject]
|
||||
private DownloadService DownloadService { get; set; }
|
||||
[Inject]
|
||||
private ToastService ToastService { get; set; }
|
||||
|
||||
|
||||
private async Task ForcedSync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Synchronization.ForcedSync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ThingsGateway.Plugin.Synchronization;
|
||||
|
||||
public class VariableDataWithValue
|
||||
{
|
||||
/// <inheritdoc cref="Variable.Name"/>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.Value"/>
|
||||
public object RawValue { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.CollectTime"/>
|
||||
public DateTime CollectTime { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.IsOnline"/>
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.LastErrorMessage"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? LastErrorMessage { get; set; }
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<Import Project="..\..\Version.props" />
|
||||
<Import Project="..\..\PackNuget.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj">
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
|
@@ -62,11 +62,6 @@
|
||||
<IncludeAssets> native;</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
|
||||
<PackageReference Include="ThingsGateway.Plugin.Synchronization" Version="$(PluginVersion)" GeneratePathProperty="true">
|
||||
<Private>false</Private>
|
||||
<IncludeAssets> native;</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
@@ -95,7 +90,6 @@
|
||||
<PkgThingsGateway_Plugin_OpcUaPackageFiles Include="$(PkgThingsGateway_Plugin_OpcUa)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_RabbitMQPackageFiles Include="$(PkgThingsGateway_Plugin_RabbitMQ)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_HttpPackageFiles Include="$(PkgThingsGateway_Plugin_Http)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
<PkgThingsGateway_Plugin_SynchronizationPackageFiles Include="$(PkgThingsGateway_Plugin_Synchronization)\Content\$(PluginTargetFramework)\**\*.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<Message Text="将插件复制到插件目录 $(GatewayPluginFolder)" Importance="high" />
|
||||
@@ -109,7 +103,6 @@
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_OpcUaPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.OpcUa%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_RabbitMQPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.RabbitMQ%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_HttpPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.Http%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(PkgThingsGateway_Plugin_SynchronizationPackageFiles)" DestinationFolder="$(GatewayPluginFolder)ThingsGateway.Plugin.Synchronization%(RecursiveDir)" />
|
||||
|
||||
</Target>
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user