mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 23:53:58 +08:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
			10.11.4.0
			...
			10.11.19.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9ea9529a5f | ||
|   | 4e6be23aac | ||
|   | 2fabbd236b | ||
|   | 163a66530e | ||
|   | 29073a00c4 | ||
|   | c6d4d1ecfa | ||
|   | ba16889cad | ||
|   | 5aaed35b0f | ||
|   | df067c91eb | ||
|   | 2078b4a60b | ||
|   | 20a2e3ff8e | ||
|   | 61a973b1b5 | ||
|   | cbd72e2081 | ||
|   | 4e0377b20c | ||
|   | fd318d3cdc | ||
|   | 515bdb9700 | ||
|   | 46c1780017 | ||
|   | fe78a4c3ca | 
| @@ -15,6 +15,7 @@ using System.Text; | ||||
|  | ||||
| using ThingsGateway.Admin.Application; | ||||
| using ThingsGateway.DB; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Log; | ||||
|  | ||||
| namespace ThingsGateway.AdminServer; | ||||
| @@ -64,7 +65,7 @@ public class Program | ||||
|             else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) | ||||
|                 builder.Host.UseSystemd(); | ||||
|  | ||||
|             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||||
|             if (Runtime.IsLegacyWindows) | ||||
|                 builder.Logging.ClearProviders(); //去除默认的事件日志提供者,某些情况下会日志输出异常,导致程序崩溃 | ||||
|         }).ConfigureBuilder(builder => | ||||
|         { | ||||
|   | ||||
| @@ -183,6 +183,8 @@ public class Startup : AppStartup | ||||
|         services.AddScoped<IAuthorizationHandler, BlazorServerAuthenticationHandler>(); | ||||
|         services.AddScoped<AuthenticationStateProvider, BlazorServerAuthenticationStateProvider>(); | ||||
|  | ||||
|         if (!NewLife.Runtime.IsLegacyWindows) | ||||
|         { | ||||
| #if NET9_0_OR_GREATER | ||||
|             var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet); | ||||
| #else | ||||
| @@ -197,6 +199,7 @@ public class Startup : AppStartup | ||||
|                     ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env) | ||||
|     { | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" /> | ||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.9.2" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.10.0" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -31,7 +31,7 @@ | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" /> | ||||
| 		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" /> | ||||
| 		<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET9Version)" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|   | ||||
							
								
								
									
										45
									
								
								src/Admin/ThingsGateway.NewLife.X/Common/BoundedQueue.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/Admin/ThingsGateway.NewLife.X/Common/BoundedQueue.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| namespace ThingsGateway.NewLife; | ||||
|  | ||||
| using System; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| public class BoundedQueue<T> : IEnumerable<T> | ||||
| { | ||||
|     private readonly Queue<T> _queue; | ||||
|     private readonly int _capacity; | ||||
|     private readonly object _syncRoot = new object(); | ||||
|  | ||||
|     public BoundedQueue(int capacity) | ||||
|     { | ||||
|         if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); | ||||
|         _capacity = capacity; | ||||
|         _queue = new Queue<T>(capacity); | ||||
|     } | ||||
|  | ||||
|     public void Enqueue(T item) | ||||
|     { | ||||
|         lock (_syncRoot) | ||||
|         { | ||||
|             if (_queue.Count == _capacity) | ||||
|                 _queue.Dequeue(); | ||||
|             _queue.Enqueue(item); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public int Count | ||||
|     { | ||||
|         get { lock (_syncRoot) return _queue.Count; } | ||||
|     } | ||||
|  | ||||
|     public IEnumerator<T> GetEnumerator() | ||||
|     { | ||||
|         lock (_syncRoot) | ||||
|         { | ||||
|             return new List<T>(_queue).GetEnumerator(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,48 @@ | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| using ThingsGateway.NewLife.Threading; | ||||
| namespace ThingsGateway.NewLife; | ||||
|  | ||||
| public class ExpiringDictionary<TKey, TValue> : IDisposable | ||||
| { | ||||
|     private readonly ConcurrentDictionary<TKey, TValue> _dict = new(); | ||||
|     private readonly TimerX _cleanupTimer; | ||||
|  | ||||
|     public ExpiringDictionary(int cleanupInterval = 60000) | ||||
|     { | ||||
|         _cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true }; | ||||
|     } | ||||
|  | ||||
|     public void TryAdd(TKey key, TValue value) | ||||
|     { | ||||
|         _dict.TryAdd(key, value); | ||||
|     } | ||||
|  | ||||
|     public bool TryGetValue(TKey key, out TValue value) | ||||
|     { | ||||
|         return _dict.TryGetValue(key, out value); | ||||
|     } | ||||
|     public TValue GetOrAdd(TKey key, Func<TKey, TValue> func) | ||||
|     { | ||||
|         return _dict.GetOrAdd(key, func); | ||||
|     } | ||||
|     public TValue GetOrAdd(TKey key, TValue value) | ||||
|     { | ||||
|         return _dict.GetOrAdd(key, value); | ||||
|     } | ||||
|  | ||||
|     public bool TryRemove(TKey key) => _dict.TryRemove(key, out _); | ||||
|  | ||||
|     public void Clear() => _dict.Clear(); | ||||
|  | ||||
|     private void Clear(object? state) | ||||
|     { | ||||
|         _dict.Clear(); | ||||
|     } | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|         _dict.Clear(); | ||||
|         _cleanupTimer.Dispose(); | ||||
|     } | ||||
| } | ||||
| @@ -103,6 +103,54 @@ public static class Runtime | ||||
|  | ||||
|     /// <summary>是否OSX环境</summary> | ||||
|     public static Boolean OSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); | ||||
|  | ||||
| #if NET6_0_OR_GREATER | ||||
|  | ||||
|     public static Boolean? isLegacyWindows; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 判断是否老系统 (Vista/2008/7/2008R2) | ||||
|     /// </summary> | ||||
|     public static Boolean IsLegacyWindows | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             if (isLegacyWindows != null) return isLegacyWindows.Value; | ||||
|  | ||||
|             if (Windows == false) | ||||
|             { | ||||
|                 isLegacyWindows = false; | ||||
|                 return isLegacyWindows.Value; | ||||
|             } | ||||
|             var version = Environment.OSVersion.Version; | ||||
|  | ||||
|             // 如果能拿到真实的 6.x 就直接判断 | ||||
|             if (version.Major == 6 && version.Minor <= 1) | ||||
|             { | ||||
|                 isLegacyWindows = true; | ||||
|                 return isLegacyWindows.Value; | ||||
|             } | ||||
|             if (version.Major < 6) | ||||
|             { | ||||
|                 isLegacyWindows = true; | ||||
|                 return isLegacyWindows.Value; | ||||
|             } | ||||
|  | ||||
|             // 如果拿到的是 10.0(Win8.1 之后有虚拟化问题),用 OSDescription 来兜底 | ||||
|             var desc = RuntimeInformation.OSDescription; | ||||
|             // desc 示例: "Microsoft Windows 6.1.7601" (Win7/2008R2) | ||||
|             if (desc.Contains("Windows 6.0") || desc.Contains("Windows 6.1")) | ||||
|             { | ||||
|                 isLegacyWindows = true; | ||||
|                 return isLegacyWindows.Value; | ||||
|             } | ||||
|             isLegacyWindows = false; | ||||
|             return isLegacyWindows.Value; | ||||
|         } | ||||
|  | ||||
|     } | ||||
| #endif | ||||
|  | ||||
| #else | ||||
|     /// <summary>是否Web环境</summary> | ||||
|     public static Boolean IsWeb => !String.IsNullOrEmpty(System.Web.HttpRuntime.AppDomainAppId); | ||||
| @@ -115,6 +163,8 @@ public static class Runtime | ||||
|  | ||||
|     /// <summary>是否OSX环境</summary> | ||||
|     public static Boolean OSX { get; } = Environment.OSVersion.Platform == PlatformID.MacOSX; | ||||
|  | ||||
|  | ||||
| #endif | ||||
|     #endregion | ||||
|  | ||||
|   | ||||
| @@ -21,6 +21,7 @@ namespace ThingsGateway.NewLife.Json.Extension; | ||||
| /// </summary> | ||||
| public static class SystemTextJsonExtension | ||||
| { | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 默认Json规则(带缩进) | ||||
|     /// </summary> | ||||
| @@ -31,37 +32,51 @@ public static class SystemTextJsonExtension | ||||
|     /// </summary> | ||||
|     public static JsonSerializerOptions NoneIndentedOptions; | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 默认Json规则(带缩进) | ||||
|     /// </summary> | ||||
|     public static JsonSerializerOptions IgnoreNullIndentedOptions; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 默认Json规则(无缩进) | ||||
|     /// </summary> | ||||
|     public static JsonSerializerOptions IgnoreNullNoneIndentedOptions; | ||||
|  | ||||
|     public static JsonSerializerOptions GetOptions(bool writeIndented, bool ignoreNull) | ||||
|     { | ||||
|         var options = new JsonSerializerOptions | ||||
|         { | ||||
|             Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, | ||||
|             WriteIndented = writeIndented, | ||||
|             DefaultIgnoreCondition = ignoreNull | ||||
|                 ? JsonIgnoreCondition.WhenWritingNull | ||||
|                 : JsonIgnoreCondition.Never, | ||||
|             NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, | ||||
|         }; | ||||
|  | ||||
|         options.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson()); | ||||
|         options.Converters.Add(new JTokenSystemTextJsonConverter()); | ||||
|         options.Converters.Add(new JValueSystemTextJsonConverter()); | ||||
|         options.Converters.Add(new JObjectSystemTextJsonConverter()); | ||||
|         options.Converters.Add(new JArraySystemTextJsonConverter()); | ||||
|  | ||||
|         return options; | ||||
|     } | ||||
|  | ||||
|     static SystemTextJsonExtension() | ||||
|     { | ||||
|         IndentedOptions = new JsonSerializerOptions | ||||
|         { | ||||
|             Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, | ||||
|             WriteIndented = true, // 缩进 | ||||
|             DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 忽略 null | ||||
|             NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, | ||||
|         }; | ||||
|         // 如有自定义Converter,这里添加 | ||||
|         // IndentedOptions.Converters.Add(new ByteArrayJsonConverter()); | ||||
|         IndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson()); | ||||
|         IndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter()); | ||||
|         IndentedOptions.Converters.Add(new JValueSystemTextJsonConverter()); | ||||
|         IndentedOptions.Converters.Add(new JObjectSystemTextJsonConverter()); | ||||
|         IndentedOptions.Converters.Add(new JArraySystemTextJsonConverter()); | ||||
|         NoneIndentedOptions = new JsonSerializerOptions | ||||
|         { | ||||
|             Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, | ||||
|             WriteIndented = false, // 不缩进 | ||||
|             DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, | ||||
|             NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, | ||||
|         }; | ||||
|         NoneIndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson()); | ||||
|         NoneIndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter()); | ||||
|         NoneIndentedOptions.Converters.Add(new JValueSystemTextJsonConverter()); | ||||
|         NoneIndentedOptions.Converters.Add(new JObjectSystemTextJsonConverter()); | ||||
|         NoneIndentedOptions.Converters.Add(new JArraySystemTextJsonConverter()); | ||||
|         // NoneIndentedOptions.Converters.Add(new ByteArrayJsonConverter()); | ||||
|  | ||||
|         IndentedOptions = GetOptions(true, false); | ||||
|         NoneIndentedOptions = GetOptions(false, false); | ||||
|  | ||||
|         IgnoreNullIndentedOptions = GetOptions(true, true); | ||||
|         IgnoreNullNoneIndentedOptions = GetOptions(false, true); | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 反序列化 | ||||
|     /// </summary> | ||||
| @@ -96,17 +111,17 @@ public static class SystemTextJsonExtension | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     public static string ToSystemTextJsonString(this object item, bool indented = true) | ||||
|     public static string ToSystemTextJsonString(this object item, bool indented = true, bool ignoreNull = true) | ||||
|     { | ||||
|         return JsonSerializer.Serialize(item, item?.GetType() ?? typeof(object), indented ? IndentedOptions : NoneIndentedOptions); | ||||
|         return JsonSerializer.Serialize(item, item?.GetType() ?? typeof(object), ignoreNull ? indented ? IgnoreNullIndentedOptions : IgnoreNullNoneIndentedOptions : indented ? IndentedOptions : NoneIndentedOptions); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 序列化 | ||||
|     /// </summary> | ||||
|     public static byte[] ToSystemTextJsonUtf8Bytes(this object item, bool indented = true) | ||||
|     public static byte[] ToSystemTextJsonUtf8Bytes(this object item, bool indented = true, bool ignoreNull = true) | ||||
|     { | ||||
|         return JsonSerializer.SerializeToUtf8Bytes(item, item.GetType(), indented ? IndentedOptions : NoneIndentedOptions); | ||||
|         return JsonSerializer.SerializeToUtf8Bytes(item, item.GetType(), ignoreNull ? indented ? IgnoreNullIndentedOptions : IgnoreNullNoneIndentedOptions : indented ? IndentedOptions : NoneIndentedOptions); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -552,19 +552,29 @@ public static class Reflect | ||||
|     //    return false; | ||||
|     //} | ||||
|  | ||||
|  | ||||
|     private static readonly ExpiringDictionary<(MethodInfo, Type, object?), Delegate> _delegateCache = new(); | ||||
|  | ||||
|     /// <summary>把一个方法转为泛型委托,便于快速反射调用</summary> | ||||
|     /// <typeparam name="TFunc"></typeparam> | ||||
|     /// <param name="method"></param> | ||||
|     /// <param name="target"></param> | ||||
|     /// <returns></returns> | ||||
|     public static TFunc? As<TFunc>(this MethodInfo method, Object? target = null) | ||||
|     public static TFunc? As<TFunc>(this MethodInfo method, object? target = null) | ||||
|     { | ||||
|         if (method == null) return default; | ||||
|  | ||||
|         if (target == null) | ||||
|             return (TFunc?)(Object?)Delegate.CreateDelegate(typeof(TFunc), method, true); | ||||
|         else | ||||
|             return (TFunc?)(Object?)Delegate.CreateDelegate(typeof(TFunc), target, method, true); | ||||
|         var key = (method, typeof(TFunc), target); | ||||
|  | ||||
|         if (_delegateCache.TryGetValue(key, out var del)) | ||||
|             return (TFunc)(object)del; | ||||
|  | ||||
|         del = target == null | ||||
|             ? Delegate.CreateDelegate(typeof(TFunc), method, true) | ||||
|             : Delegate.CreateDelegate(typeof(TFunc), target, method, true); | ||||
|  | ||||
|         return (TFunc)(object)_delegateCache.GetOrAdd(key, del); | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
| } | ||||
| @@ -168,12 +168,13 @@ public class TimerScheduler : ILogFeature | ||||
|                 _period = 60_000; | ||||
|                 foreach (var timer in arr) | ||||
|                 { | ||||
|                     if (!timer.Calling && CheckTime(timer, now)) | ||||
|                     if ((timer.Reentrant || !timer.Calling) && CheckTime(timer, now)) | ||||
|                     { | ||||
|                         // 必须在主线程设置状态,否则可能异步线程还没来得及设置开始状态,主线程又开始了新的一轮调度 | ||||
|                         timer.Calling = true; | ||||
|                         if (timer.IsAsyncTask) | ||||
|                             Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); | ||||
|                             ExecuteAsync(timer); | ||||
|                         //Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); | ||||
|                         else if (!timer.Async) | ||||
|                             Execute(timer); | ||||
|                         else | ||||
| @@ -310,13 +311,17 @@ public class TimerScheduler : ILogFeature | ||||
|             if (timer.IsValueTask) | ||||
|             { | ||||
|                 var func = timer.Method.As<Func<Object?, ValueTask>>(target); | ||||
|                 await func!(timer.State).ConfigureAwait(false); | ||||
|                 var task = func!(timer.State); | ||||
|                 if (!task.IsCompleted) | ||||
|                     await task.ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
| #endif | ||||
|             { | ||||
|                 var func = timer.Method.As<Func<Object?, Task>>(target); | ||||
|                 await func!(timer.State).ConfigureAwait(false); | ||||
|                 var task = func!(timer.State); | ||||
|                 if (!task.IsCompleted) | ||||
|                     await task.ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|   | ||||
| @@ -72,7 +72,8 @@ public class TimerX : ITimer, ITimerx, IDisposable | ||||
|  | ||||
|     /// <summary>调用中</summary> | ||||
|     public Boolean Calling { get; internal set; } | ||||
|  | ||||
|     /// <summary>可重入</summary> | ||||
|     public Boolean Reentrant { get; set; } = false; | ||||
|     /// <summary>平均耗时。毫秒</summary> | ||||
|     public Int32 Cost { get; internal set; } | ||||
|  | ||||
|   | ||||
| @@ -23,8 +23,8 @@ | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="SqlSugarCore.Dm" Version="8.8.0" /> | ||||
| 		<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.728" /> | ||||
| 		<PackageReference Include="SqlSugarCore.Dm" Version="8.8.1" /> | ||||
| 		<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.821" /> | ||||
| 		<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" /> | ||||
| 		<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET9Version)" />--> | ||||
| 		<PackageReference Include="MySqlConnector" Version="2.4.0" /> | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <Project> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<PluginVersion>10.11.4</PluginVersion> | ||||
| 		<ProPluginVersion>10.11.4</ProPluginVersion> | ||||
| 		<DefaultVersion>10.11.4</DefaultVersion> | ||||
| 		<PluginVersion>10.11.19</PluginVersion> | ||||
| 		<ProPluginVersion>10.11.19</ProPluginVersion> | ||||
| 		<DefaultVersion>10.11.19</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.11.2</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.11.2</SourceGeneratorVersion> | ||||
| 		<NET8Version>8.0.19</NET8Version> | ||||
| @@ -28,7 +28,8 @@ | ||||
| 		<AnalysisModeStyle>None</AnalysisModeStyle> | ||||
|  | ||||
| 		<NoWarn> | ||||
| 			CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA2000;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;NETSDK1206 | ||||
| 			CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA2000;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;RCS1102;RCS1194;NETSDK1206 | ||||
|  | ||||
| 		</NoWarn> | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
| 		<LangVersion>13.0</LangVersion> | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -82,13 +82,14 @@ public static class CSharpScriptEngineExtension | ||||
|     { | ||||
|         if (source.IsNullOrEmpty()) return null; | ||||
|         var field = $"{CacheKey}-{source}"; | ||||
|         var exfield = $"{CacheKey}-Exception-{source}"; | ||||
|         var runScript = Instance.Get<T>(field); | ||||
|         if (runScript == null) | ||||
|         { | ||||
|             lock (m_waiterLock) | ||||
|             { | ||||
|                 runScript = Instance.Get<T>(field); | ||||
|                 if (runScript == null) | ||||
|                 var hasValue = Instance.TryGetValue<T>(field, out runScript); | ||||
|                 if (hasValue == false) | ||||
|                 { | ||||
|                     var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); | ||||
|                     var _using = new StringBuilder(); | ||||
| @@ -111,8 +112,6 @@ public static class CSharpScriptEngineExtension | ||||
|                     } | ||||
|                     try | ||||
|                     { | ||||
|  | ||||
|  | ||||
|                         // 动态加载并执行代码 | ||||
|                         runScript = evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<T>( | ||||
|                            $@" | ||||
| @@ -140,11 +139,22 @@ public static class CSharpScriptEngineExtension | ||||
|                         string exString = string.Format(CSScriptResource.CSScriptResource.Error1, typeof(T).FullName); | ||||
|                         throw new(exString); | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         //如果编译失败,应该不重复编译,避免oom | ||||
|                         Instance.Set<T>(field, null, TimeSpan.FromHours(1)); | ||||
|                         Instance.Set(exfield, ex, TimeSpan.FromHours(1)); | ||||
|                         throw; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         Instance.SetExpire(field, TimeSpan.FromHours(1)); | ||||
|  | ||||
|         Instance.SetExpire(exfield, TimeSpan.FromHours(1)); | ||||
|         if (runScript == null) | ||||
|         { | ||||
|             throw (Instance.Get<Exception>(exfield) ?? new Exception("compilation error")); | ||||
|         } | ||||
|         return runScript; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -93,9 +93,15 @@ public static class ExpressionEvaluatorExtension | ||||
|     public static ReadWriteExpressions GetOrAddScript(string source) | ||||
|     { | ||||
|         var field = $"{CacheKey}-{source}"; | ||||
|         var exfield = $"{CacheKey}-Exception-{source}"; | ||||
|         var runScript = Instance.Get<ReadWriteExpressions>(field); | ||||
|         if (runScript == null) | ||||
|         { | ||||
|             var hasValue = Instance.TryGetValue<ReadWriteExpressions>(field, out runScript); | ||||
|             if (!hasValue) | ||||
|             { | ||||
|  | ||||
|  | ||||
|                 if (!source.Contains("return")) | ||||
|                 { | ||||
|                     source = $"return {source}";//只判断简单脚本中可省略return字符串 | ||||
| @@ -115,8 +121,10 @@ public static class ExpressionEvaluatorExtension | ||||
|                     } | ||||
|                 }); | ||||
|                 // 动态加载并执行代码 | ||||
|                 try | ||||
|                 { | ||||
|                     runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<ReadWriteExpressions>( | ||||
|                 $@" | ||||
| $@" | ||||
|         using System; | ||||
|         using System.Linq; | ||||
|         using System.Collections.Generic; | ||||
| @@ -139,7 +147,24 @@ public static class ExpressionEvaluatorExtension | ||||
|     "); | ||||
|                     Instance.Set(field, runScript); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     //如果编译失败,应该不重复编译,避免oom | ||||
|                     Instance.Set<ReadWriteExpressions>(field, null, TimeSpan.FromHours(1)); | ||||
|                     Instance.Set(exfield, ex, TimeSpan.FromHours(1)); | ||||
|                     throw; | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         Instance.SetExpire(field, TimeSpan.FromHours(1)); | ||||
|         Instance.SetExpire(exfield, TimeSpan.FromHours(1)); | ||||
|         if (runScript == null) | ||||
|         { | ||||
|             throw (Instance.Get<Exception>(exfield) ?? new Exception("compilation error")); | ||||
|         } | ||||
|         return runScript; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,9 @@ public static class ChannelOptionsExtensions | ||||
|             for (int i = 0; i < funcs.Count; i++) | ||||
|             { | ||||
|                 var func = funcs[i]; | ||||
|                 await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false); | ||||
|                 var task = func.Invoke(clientChannel, e, i == funcs.Count - 1); | ||||
|                 if (!task.IsCompleted) | ||||
|                     await task.ConfigureAwait(false); | ||||
|                 if (e.Handled) | ||||
|                 { | ||||
|                     break; | ||||
|   | ||||
| @@ -31,7 +31,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel | ||||
|     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (_deviceDataHandleAdapter == null && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (_deviceDataHandleAdapter != ReadOnlyDataHandlingAdapter && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             _deviceDataHandleAdapter = handleAdapter; | ||||
|         } | ||||
|   | ||||
| @@ -66,7 +66,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin | ||||
|         { | ||||
|             if (!socket.Id.StartsWith("ID=")) | ||||
|             { | ||||
|                 var id = DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.ToString()}"; | ||||
|                 var id = DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.TotalSequence.ToString(Encoding.UTF8)}"; | ||||
|                 if (tcpServiceChannel.TryGetClient(id, out var oldClient)) | ||||
|                 { | ||||
|                     try | ||||
| @@ -80,6 +80,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin | ||||
|                 } | ||||
|                 await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false); | ||||
|                 client.Logger?.Info(string.Format(AppResource.DtuConnected, id)); | ||||
|                 e.Reader.Advance((int)e.Reader.BytesRemaining); | ||||
|                 e.Handled = true; | ||||
|             } | ||||
|  | ||||
| @@ -100,7 +101,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin | ||||
|  | ||||
|             if (len > 0) | ||||
|             { | ||||
|                 if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, len).First.Span)) | ||||
|                 if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span)) | ||||
|                 { | ||||
|                     if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200)) | ||||
|                     { | ||||
| @@ -108,6 +109,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin | ||||
|                     } | ||||
|                     //回应心跳包 | ||||
|                     await socket.SendAsync(HeartbeatByte, socket.ClosedToken).ConfigureAwait(false); | ||||
|                     e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining)); | ||||
|                     e.Handled = true; | ||||
|                     if (socket.Logger?.LogLevel <= LogLevel.Trace) | ||||
|                         socket.Logger?.Trace($"{socket}- Heartbeat"); | ||||
|   | ||||
| @@ -181,8 +181,9 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi | ||||
|             var len = HeartbeatByte.Length; | ||||
|             if (len > 0) | ||||
|             { | ||||
|                 if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, len).First.Span)) | ||||
|                 if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span)) | ||||
|                 { | ||||
|                     e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining)); | ||||
|                     e.Handled = true; | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -50,7 +50,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | ||||
|     private IDeviceDataHandleAdapter _deviceDataHandleAdapter; | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (_deviceDataHandleAdapter == null && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (_deviceDataHandleAdapter != ProtectedDataHandlingAdapter && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             _deviceDataHandleAdapter = handleAdapter; | ||||
|         } | ||||
| @@ -192,12 +192,17 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnSerialReceived(ReceivedDataEventArgs e) | ||||
|     { | ||||
|         await base.OnSerialReceived(e).ConfigureAwait(false); | ||||
|         var receivedTask = base.OnSerialReceived(e); | ||||
|         if (!receivedTask.IsCompleted) | ||||
|             await receivedTask.ConfigureAwait(false); | ||||
|  | ||||
|         if (e.Handled) | ||||
|             return; | ||||
|  | ||||
|         await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false); | ||||
|         var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived); | ||||
|         if (!channelReceivedTask.IsCompleted) | ||||
|             await channelReceivedTask.ConfigureAwait(false); | ||||
|  | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     protected override void SafetyDispose(bool disposing) | ||||
|   | ||||
| @@ -33,7 +33,7 @@ public class TcpClientChannel : TcpClient, IClientChannel | ||||
|     private IDeviceDataHandleAdapter _deviceDataHandleAdapter; | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             _deviceDataHandleAdapter = handleAdapter; | ||||
|         } | ||||
| @@ -179,12 +179,16 @@ public class TcpClientChannel : TcpClient, IClientChannel | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnTcpReceived(ReceivedDataEventArgs e) | ||||
|     { | ||||
|         await base.OnTcpReceived(e).ConfigureAwait(false); | ||||
|         var receivedTask = base.OnTcpReceived(e); | ||||
|         if (!receivedTask.IsCompleted) | ||||
|             await receivedTask.ConfigureAwait(false); | ||||
|  | ||||
|         if (e.Handled) | ||||
|             return; | ||||
|  | ||||
|         await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false); | ||||
|         var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived); | ||||
|         if (!channelReceivedTask.IsCompleted) | ||||
|             await channelReceivedTask.ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -203,8 +203,8 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann | ||||
|         data.ResetSign(MinSign, MaxSign); | ||||
|         return data; | ||||
|     } | ||||
|     public int MaxSign { get; private set; } = 0; | ||||
|     public int MinSign { get; private set; } = ushort.MaxValue; | ||||
|     public int MinSign { get; private set; } = 0; | ||||
|     public int MaxSign { get; private set; } = ushort.MaxValue; | ||||
|     public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue) | ||||
|     { | ||||
|         MinSign = minSign; | ||||
| @@ -241,12 +241,17 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnTcpReceived(TClient socketClient, ReceivedDataEventArgs e) | ||||
|     { | ||||
|         await base.OnTcpReceived(socketClient, e).ConfigureAwait(false); | ||||
|         var receivedTask = base.OnTcpReceived(socketClient, e); | ||||
|         if (!receivedTask.IsCompleted) | ||||
|             await receivedTask.ConfigureAwait(false); | ||||
|  | ||||
|         if (e.Handled) | ||||
|             return; | ||||
|  | ||||
|         await socketClient.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false); | ||||
|         var channelReceivedTask = socketClient.OnChannelReceivedEvent(e, ChannelReceived); | ||||
|         if (!channelReceivedTask.IsCompleted) | ||||
|             await channelReceivedTask.ConfigureAwait(false); | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel | ||||
|     private IDeviceDataHandleAdapter _deviceDataHandleAdapter; | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             _deviceDataHandleAdapter = handleAdapter; | ||||
|         } | ||||
| @@ -145,11 +145,15 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnTcpReceived(ReceivedDataEventArgs e) | ||||
|     { | ||||
|         await base.OnTcpReceived(e).ConfigureAwait(false); | ||||
|         var receivedTask = base.OnTcpReceived(e); | ||||
|         if (!receivedTask.IsCompleted) | ||||
|             await receivedTask.ConfigureAwait(false); | ||||
|  | ||||
|         if (e.Handled) | ||||
|             return; | ||||
|  | ||||
|         await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false); | ||||
|         var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived); | ||||
|         if (!channelReceivedTask.IsCompleted) | ||||
|             await channelReceivedTask.ConfigureAwait(false); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -29,7 +29,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel | ||||
|     private IDeviceDataHandleAdapter _deviceDataHandleAdapter; | ||||
|     public void SetDataHandlingAdapterLogger(ILog log) | ||||
|     { | ||||
|         if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter) | ||||
|         { | ||||
|             _deviceDataHandleAdapter = handleAdapter; | ||||
|         } | ||||
| @@ -196,12 +196,17 @@ public class UdpSessionChannel : UdpSession, IClientChannel | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task OnUdpReceived(UdpReceivedDataEventArgs e) | ||||
|     { | ||||
|         await base.OnUdpReceived(e).ConfigureAwait(false); | ||||
|         var receivedTask = base.OnUdpReceived(e); | ||||
|         if (!receivedTask.IsCompleted) | ||||
|             await receivedTask.ConfigureAwait(false); | ||||
|  | ||||
|         if (e.Handled) | ||||
|             return; | ||||
|  | ||||
|         await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false); | ||||
|         var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived); | ||||
|         if (!channelReceivedTask.IsCompleted) | ||||
|             await channelReceivedTask.ConfigureAwait(false); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -10,6 +10,8 @@ | ||||
|  | ||||
| using System.Text; | ||||
|  | ||||
| using ThingsGateway.NewLife.Collections; | ||||
|  | ||||
| namespace ThingsGateway.Foundation; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -17,7 +19,19 @@ namespace ThingsGateway.Foundation; | ||||
| /// </summary> | ||||
| public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingAdapter<TRequest>, IDeviceDataHandleAdapter where TRequest : MessageBase, new() | ||||
| { | ||||
|     public new ILog Logger { get; set; } | ||||
|     private ILog logger; | ||||
|  | ||||
|     public new ILog Logger | ||||
|     { | ||||
|         get => logger ?? base.Logger; | ||||
|         set | ||||
|         { | ||||
|             if (value != logger && value != null) | ||||
|             { | ||||
|                 logger = value; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc cref="DeviceSingleStreamDataHandleAdapter{TRequest}"/> | ||||
| @@ -45,6 +59,13 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingA | ||||
|     /// <inheritdoc /> | ||||
|     public void SetRequest(ISendMessage sendMessage) | ||||
|     { | ||||
|         if (IsSingleThread) | ||||
|         { | ||||
|             if (Request != null) | ||||
|             { | ||||
|                 _requestPool.Return(Request); | ||||
|             } | ||||
|         } | ||||
|         var request = GetInstance(); | ||||
|         request.Sign = sendMessage.Sign; | ||||
|         request.SendInfo(sendMessage); | ||||
| @@ -144,15 +165,26 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingA | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static ObjectPool<TRequest> _requestPool { get; } = new ObjectPool<TRequest>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取泛型实例。 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected virtual TRequest GetInstance() | ||||
|     { | ||||
|         if (IsSingleThread) | ||||
|         { | ||||
|             var request = _requestPool.Get(); | ||||
|             request.OperCode = -1; | ||||
|             request.Sign = -1; | ||||
|             return request; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return new TRequest() { OperCode = -1, Sign = -1 }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void SendInput<TWriter>(ref TWriter writer, in ReadOnlyMemory<byte> memory) | ||||
|     { | ||||
|   | ||||
| @@ -11,6 +11,8 @@ | ||||
| using System.Net; | ||||
| using System.Text; | ||||
|  | ||||
| using ThingsGateway.NewLife.Collections; | ||||
|  | ||||
| namespace ThingsGateway.Foundation; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -18,7 +20,19 @@ namespace ThingsGateway.Foundation; | ||||
| /// </summary> | ||||
| public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter, IDeviceDataHandleAdapter where TRequest : MessageBase, new() | ||||
| { | ||||
|     public new ILog Logger { get; set; } | ||||
|     private ILog logger; | ||||
|  | ||||
|     public new ILog Logger | ||||
|     { | ||||
|         get => logger ?? base.Logger; | ||||
|         set | ||||
|         { | ||||
|             if (value != logger && value != null) | ||||
|             { | ||||
|                 logger = value; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override bool CanSendRequestInfo => true; | ||||
| @@ -38,6 +52,13 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter, IDev | ||||
|     /// <inheritdoc /> | ||||
|     public void SetRequest(ISendMessage sendMessage) | ||||
|     { | ||||
|         if (IsSingleThread) | ||||
|         { | ||||
|             if (Request != null) | ||||
|             { | ||||
|                 _requestPool.Return(Request); | ||||
|             } | ||||
|         } | ||||
|         var request = GetInstance(); | ||||
|         request.Sign = sendMessage.Sign; | ||||
|         request.SendInfo(sendMessage); | ||||
| @@ -50,14 +71,27 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter, IDev | ||||
|         return Owner.ToString(); | ||||
|     } | ||||
|  | ||||
|     private static ObjectPool<TRequest> _requestPool { get; } = new ObjectPool<TRequest>(); | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取泛型实例。 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected virtual TRequest GetInstance() | ||||
|     { | ||||
|         if (IsSingleThread) | ||||
|         { | ||||
|             var request = _requestPool.Get(); | ||||
|             request.OperCode = -1; | ||||
|             request.Sign = -1; | ||||
|             return request; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return new TRequest() { OperCode = -1, Sign = -1 }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     #region ParseRequest | ||||
|   | ||||
| @@ -348,11 +348,19 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|  | ||||
|             if (channel is IDtuUdpSessionChannel udpSession) | ||||
|             { | ||||
|                 await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false); | ||||
|                 var sendTask = udpSession.SendAsync(endPoint, sendMessage, token); | ||||
|                 if (!sendTask.IsCompleted) | ||||
|                 { | ||||
|                     await sendTask.ConfigureAwait(false); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 await channel.SendAsync(sendMessage, token).ConfigureAwait(false); | ||||
|                 var sendTask = channel.SendAsync(sendMessage, token); | ||||
|                 if (!sendTask.IsCompleted) | ||||
|                 { | ||||
|                     await sendTask.ConfigureAwait(false); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return OperResult.Success; | ||||
| @@ -363,13 +371,17 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private Task BefortSendAsync(IClientChannel channel, CancellationToken token) | ||||
|     private Task BeforeSendAsync(IClientChannel channel, CancellationToken token) | ||||
|     { | ||||
|         SetDataAdapter(channel); | ||||
|  | ||||
|         if (AutoConnect && Channel != null && Channel?.Online != true) | ||||
|         { | ||||
|             return ConnectAsync(token); | ||||
|  | ||||
|  | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return Task.CompletedTask; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private WaitLock connectWaitLock = new(nameof(DeviceBase)); | ||||
| @@ -410,7 +422,11 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 await BefortSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false); | ||||
|                 var beforeSendTask = BeforeSendAsync(channelResult.Content, cancellationToken); | ||||
|                 if (!beforeSendTask.IsCompleted) | ||||
|                 { | ||||
|                     await beforeSendTask.ConfigureAwait(false); | ||||
|                 } | ||||
|  | ||||
|                 await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|                 channelResult.Content.SetDataHandlingAdapterLogger(Logger); | ||||
| @@ -508,9 +524,19 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var result = await SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false); | ||||
|             var sendTask = SendThenReturnMessageAsync(sendMessage, channel, cancellationToken); | ||||
|             if (!sendTask.IsCompleted) | ||||
|             { | ||||
|                 var result = await sendTask.ConfigureAwait(false); | ||||
|                 return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content }; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var result = sendTask.Result; | ||||
|                 return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content }; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new(ex); | ||||
| @@ -541,8 +567,11 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|         WaitLock? waitLock = null; | ||||
|         try | ||||
|         { | ||||
|             await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|             var beforeSendTask = BeforeSendAsync(clientChannel, cancellationToken); | ||||
|             if (!beforeSendTask.IsCompleted) | ||||
|             { | ||||
|                 await beforeSendTask.ConfigureAwait(false); | ||||
|             } | ||||
|             var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null; | ||||
|             waitLock = GetWaitLock(clientChannel, dtuId); | ||||
|  | ||||
| @@ -559,7 +588,17 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|             if (cancellationToken.IsCancellationRequested) | ||||
|                 return new MessageBase(new OperationCanceledException()); | ||||
|  | ||||
|             var sendOperResult = await SendAsync(command, clientChannel, endPoint, cancellationToken).ConfigureAwait(false); | ||||
|             OperResult sendOperResult = default; | ||||
|             var sendTask = SendAsync(command, clientChannel, endPoint, cancellationToken); | ||||
|             if (!sendTask.IsCompleted) | ||||
|             { | ||||
|                 sendOperResult = await sendTask.ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 sendOperResult = sendTask.Result; | ||||
|             } | ||||
|  | ||||
|             if (!sendOperResult.IsSuccess) | ||||
|                 return new MessageBase(sendOperResult); | ||||
|  | ||||
| @@ -567,7 +606,11 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
|             try | ||||
|             { | ||||
|                 using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, Channel.ClosedToken); | ||||
|                 await waitData.WaitAsync(cts.Token).ConfigureAwait(false); | ||||
|                 var waitDataTask = waitData.WaitAsync(cts.Token); | ||||
|                 if (!waitDataTask.IsCompleted) | ||||
|                 { | ||||
|                     await waitDataTask.ConfigureAwait(false); | ||||
|                 } | ||||
|             } | ||||
|             catch (OperationCanceledException) | ||||
|             { | ||||
|   | ||||
| @@ -385,9 +385,7 @@ public static class ByteBlockExtension | ||||
|     } | ||||
|     public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, int pos) | ||||
|     where T : unmanaged | ||||
|     where TWriter : IBytesWriter | ||||
|     { | ||||
|         if (writer.SupportsRewind) | ||||
|     where TWriter : IByteBlockWriter | ||||
|     { | ||||
|         var nowPos = (int)writer.WrittenCount - pos; | ||||
|         writer.Advance(-nowPos); | ||||
| @@ -395,17 +393,11 @@ public static class ByteBlockExtension | ||||
|         var span = writer.GetSpan(size); | ||||
|         TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value); | ||||
|         writer.Advance(nowPos); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw new InvalidOperationException("Writer version mismatch or does not support rewind."); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|     public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, long pos) | ||||
| where T : unmanaged | ||||
| where TWriter : IBytesWriter | ||||
|     { | ||||
|         if (writer.SupportsRewind) | ||||
| where TWriter : IByteBlockWriter | ||||
|     { | ||||
|         var nowPos = (int)(writer.WrittenCount - pos); | ||||
|         writer.Advance(-nowPos); | ||||
| @@ -413,11 +405,7 @@ where TWriter : IBytesWriter | ||||
|         var span = writer.GetSpan(size); | ||||
|         TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value); | ||||
|         writer.Advance(nowPos); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw new InvalidOperationException("Writer version mismatch or does not support rewind."); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static string ReadNormalString<TReader>(ref TReader reader, int length) | ||||
| @@ -429,20 +417,32 @@ where TWriter : IBytesWriter | ||||
|         return str; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public static void WriteBackNormalString<TWriter>(ref TWriter writer, string value, Encoding encoding, int pos) | ||||
| where TWriter : IBytesWriter | ||||
|     { | ||||
|         if (writer.SupportsRewind) | ||||
| where TWriter : IByteBlockWriter | ||||
|     { | ||||
|         var nowPos = (int)(writer.WrittenCount - pos); | ||||
|         writer.Advance(-nowPos); | ||||
|         WriterExtension.WriteNormalString(ref writer, value, encoding); | ||||
|         writer.Advance(nowPos); | ||||
|  | ||||
|     } | ||||
|         else | ||||
|  | ||||
|  | ||||
|     public static int WriteNormalString(this Span<byte> span, string value, Encoding encoding) | ||||
|     { | ||||
|             throw new InvalidOperationException("Writer version mismatch or does not support rewind."); | ||||
|         var maxSize = encoding.GetMaxByteCount(value.Length); | ||||
|         var chars = value.AsSpan(); | ||||
|  | ||||
|         unsafe | ||||
|         { | ||||
|             fixed (char* p = &chars[0]) | ||||
|             { | ||||
|                 fixed (byte* p1 = &span[0]) | ||||
|                 { | ||||
|                     var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize); | ||||
|                     return len; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|   | ||||
| @@ -11,8 +11,8 @@ | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" /> | ||||
| 		<PackageReference Include="TouchSocket" Version="4.0.0-beta.1" /> | ||||
| 		<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.1" /> | ||||
| 		<PackageReference Include="TouchSocket" Version="4.0.0-beta.5" /> | ||||
| 		<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.5" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -28,7 +28,7 @@ public static class JTokenUtil | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (item.IsNullOrWhiteSpace()) | ||||
|             if (item.IsNullOrEmpty()) | ||||
|                 return JValue.CreateNull(); | ||||
|  | ||||
|             if (bool.TryParse(item, out bool parseBool)) | ||||
|   | ||||
| @@ -28,7 +28,7 @@ public class AsyncReadWriteLock : IAsyncDisposable | ||||
|     /// <summary> | ||||
|     /// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。 | ||||
|     /// </summary> | ||||
|     public async Task<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken) | ||||
|     public async ValueTask<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|  | ||||
|         if (Interlocked.Read(ref _writerCount) > 0) | ||||
| @@ -51,7 +51,7 @@ public class AsyncReadWriteLock : IAsyncDisposable | ||||
|     /// <summary> | ||||
|     /// 获取写锁,阻止所有读取。 | ||||
|     /// </summary> | ||||
|     public async Task<IDisposable> WriterLockAsync(CancellationToken cancellationToken) | ||||
|     public async ValueTask<IDisposable> WriterLockAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|  | ||||
|         if (Interlocked.Increment(ref _writerCount) == 1) | ||||
|   | ||||
| @@ -7,7 +7,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public class CronScheduledTask : DisposeBase, IScheduledTask | ||||
| { | ||||
|     private int _interval10MS = 10; | ||||
|     private int next = 10; | ||||
|     private string _interval; | ||||
|     private readonly Func<object?, CancellationToken, Task> _taskFunc; | ||||
|     private readonly Func<object?, CancellationToken, ValueTask> _valueTaskFunc; | ||||
| @@ -62,9 +62,9 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | ||||
|         _timer?.Dispose(); | ||||
|         if (Check()) return; | ||||
|         if (_taskAction != null) | ||||
|             _timer = new TimerX(TimerCallback, _state, _interval, nameof(IScheduledTask)) { Async = true }; | ||||
|             _timer = new TimerX(TimerCallback, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false }; | ||||
|         else if (_taskFunc != null || _valueTaskFunc != null) | ||||
|             _timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(IScheduledTask)) { Async = true }; | ||||
|             _timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false }; | ||||
|     } | ||||
|  | ||||
|     private async ValueTask TimerCallbackAsync(object? state) | ||||
| @@ -107,7 +107,7 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | ||||
|         { | ||||
|             if (!Check()) | ||||
|             { | ||||
|                 SetNext(_interval10MS); | ||||
|                 SetNext(next); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -149,7 +149,7 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | ||||
|         { | ||||
|             if (!Check()) | ||||
|             { | ||||
|                 SetNext(_interval10MS); | ||||
|                 SetNext(next); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntIntervalTask | ||||
| { | ||||
|     private int _interval10MS = 10; | ||||
|     private int next = 10; | ||||
|     public int IntervalMS { get; } | ||||
|     private readonly Func<object?, CancellationToken, Task> _taskFunc; | ||||
|     private readonly Func<object?, CancellationToken, ValueTask> _valueTaskFunc; | ||||
| @@ -48,7 +48,7 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte | ||||
|     { | ||||
|         _timer?.Dispose(); | ||||
|         if (!Check()) | ||||
|             _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(IScheduledTask)) { Async = true }; | ||||
|             _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { Async = true, Reentrant = false }; | ||||
|     } | ||||
|  | ||||
|     private async ValueTask DoAsync(object? state) | ||||
| @@ -70,7 +70,6 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte | ||||
|         // 减少一个触发次数 | ||||
|         Interlocked.Decrement(ref _pendingTriggers); | ||||
|  | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             if (_taskFunc != null) | ||||
| @@ -92,9 +91,9 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte | ||||
|  | ||||
|         if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1) | ||||
|         { | ||||
|             if (!Check()) | ||||
|             if (!Check() && IntervalMS > 8) | ||||
|             { | ||||
|                 SetNext(_interval10MS); | ||||
|                 SetNext(next); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntIntervalTask | ||||
| { | ||||
|     private int _interval10MS = 10; | ||||
|     private int next = 10; | ||||
|     public int IntervalMS { get; } | ||||
|     private readonly Action<object?, CancellationToken> _taskAction; | ||||
|     private readonly CancellationToken _token; | ||||
| @@ -40,7 +40,7 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter | ||||
|     { | ||||
|         _timer?.Dispose(); | ||||
|         if (!Check()) | ||||
|             _timer = new TimerX(TimerCallback, _state, IntervalMS, IntervalMS, nameof(IScheduledTask)) { Async = true }; | ||||
|             _timer = new TimerX(TimerCallback, _state, IntervalMS, IntervalMS, nameof(ScheduledSyncTask)) { Async = true, Reentrant = false }; | ||||
|     } | ||||
|  | ||||
|     private void TimerCallback(object? state) | ||||
| @@ -78,9 +78,9 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter | ||||
|  | ||||
|         if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1) | ||||
|         { | ||||
|             if (!Check()) | ||||
|             if (!Check() && IntervalMS > 8) | ||||
|             { | ||||
|                 SetNext(_interval10MS); | ||||
|                 SetNext(next); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| [RequestAudit] | ||||
| [ApiController] | ||||
| [Authorize(AuthenticationSchemes = "Bearer")] | ||||
| [TouchSocket.WebApi.Router("/miniapi/[api]/[action]")] | ||||
| [TouchSocket.WebApi.Router("/miniapi/control/[action]")] | ||||
| [TouchSocket.WebApi.EnableCors("cors")] | ||||
| public class ControlController : ControllerBase, IRpcServer | ||||
| { | ||||
|   | ||||
| @@ -27,7 +27,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| [ApiController] | ||||
| [RolePermission] | ||||
| [Authorize(AuthenticationSchemes = "Bearer")] | ||||
| [TouchSocket.WebApi.Router("/miniapi/[api]/[action]")] | ||||
| [TouchSocket.WebApi.Router("/miniapi/runtimeinfo/[action]")] | ||||
| [TouchSocket.WebApi.EnableCors("cors")] | ||||
| public class RuntimeInfoController : ControllerBase, IRpcServer | ||||
| { | ||||
|   | ||||
| @@ -18,7 +18,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| [Route("api/[controller]/[action]")] | ||||
| [AllowAnonymous] | ||||
| [ApiController] | ||||
| [TouchSocket.WebApi.Router("/miniapi/[api]/[action]")] | ||||
| [TouchSocket.WebApi.Router("/miniapi/test/[action]")] | ||||
| [TouchSocket.WebApi.EnableCors("cors")] | ||||
| public class TestController : ControllerBase, IRpcServer | ||||
| { | ||||
|   | ||||
| @@ -26,8 +26,21 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|     protected abstract BusinessPropertyWithCache _businessPropertyWithCache { get; } | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         // 清空内存队列 | ||||
|         _memoryPluginEventDataModelQueue.Clear(); | ||||
|         _memoryAlarmModelQueue.Clear(); | ||||
|         _memoryDevModelQueue.Clear(); | ||||
|         _memoryVarModelQueue.Clear(); | ||||
|         _memoryVarModelsQueue.Clear(); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     #region 条件 | ||||
|  | ||||
|     protected abstract bool PluginEventDataModelEnable { get; } | ||||
|     protected abstract bool AlarmModelEnable { get; } | ||||
|     protected abstract bool DevModelEnable { get; } | ||||
|     protected abstract bool VarModelEnable { get; } | ||||
| @@ -36,6 +49,10 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|         if (AlarmModelEnable) | ||||
|             DBCacheAlarm = LocalDBCacheAlarmModel(); | ||||
|  | ||||
|         if (PluginEventDataModelEnable) | ||||
|             DBCachePluginEventData = LocalDBCachePluginEventDataModel(); | ||||
|  | ||||
|  | ||||
|         if (DevModelEnable) | ||||
|             DBCacheDev = LocalDBCacheDevModel(); | ||||
|  | ||||
| @@ -68,7 +85,10 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|         { | ||||
|             await UpdateAlarmModelMemory(cancellationToken).ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         if (PluginEventDataModelEnable) | ||||
|         { | ||||
|             await UpdatePluginEventDataModelMemory(cancellationToken).ConfigureAwait(false); | ||||
|         } | ||||
|         if (VarModelEnable) | ||||
|         { | ||||
|             await UpdateVarModelCache(cancellationToken).ConfigureAwait(false); | ||||
| @@ -83,6 +103,12 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|         if (AlarmModelEnable) | ||||
|         { | ||||
|             await UpdateAlarmModelCache(cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|         } | ||||
|         if (PluginEventDataModelEnable) | ||||
|         { | ||||
|             await UpdatePluginEventDataModelCache(cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|         } | ||||
|     } | ||||
|     #endregion | ||||
| @@ -90,10 +116,12 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|     #region alarm | ||||
|  | ||||
|     protected ConcurrentQueue<CacheDBItem<AlarmVariable>> _memoryAlarmModelQueue = new(); | ||||
|     protected ConcurrentQueue<CacheDBItem<PluginEventData>> _memoryPluginEventDataModelQueue = new(); | ||||
|  | ||||
|     private volatile bool LocalDBCacheAlarmModelInited; | ||||
|     private CacheDB DBCacheAlarm; | ||||
|  | ||||
|     private volatile bool LocalDBCachePluginEventDataModelInited; | ||||
|     private CacheDB DBCachePluginEventData; | ||||
|     /// <summary> | ||||
|     /// 入缓存 | ||||
|     /// </summary> | ||||
| @@ -142,6 +170,55 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 入缓存 | ||||
|     /// </summary> | ||||
|     /// <param name="data"></param> | ||||
|     protected virtual void AddCache(List<CacheDBItem<PluginEventData>> data) | ||||
|     { | ||||
|         if (_businessPropertyWithCache.CacheEnable && data?.Count > 0) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 LogMessage?.LogInformation($"Add {typeof(PluginEventData).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(PluginEventData).FullName}_{nameof(PluginEventData)}"); | ||||
|                 var fullName = dir.CombinePathWithOs($"{fileStart}{CacheDBUtil.EX}"); | ||||
|  | ||||
|                 lock (cacheLock) | ||||
|                 { | ||||
|                     bool s = false; | ||||
|                     while (!s) | ||||
|                     { | ||||
|                         s = CacheDBUtil.DeleteCache(_businessPropertyWithCache.CacheFileMaxLength, fullName); | ||||
|                     } | ||||
|                     using var cache = LocalDBCachePluginEventDataModel(); | ||||
|                     cache.DBProvider.Fastest<CacheDBItem<PluginEventData>>().PageSize(50000).BulkCopy(data); | ||||
|                 } | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     using var cache = LocalDBCachePluginEventDataModel(); | ||||
|                     lock (cache.CacheDBOption) | ||||
|                     { | ||||
|                         cache.DBProvider.Fastest<CacheDBItem<PluginEventData>>().PageSize(50000).BulkCopy(data); | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     LogMessage?.LogWarning(ex, "Add cache fail"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加队列,超限后会入缓存 | ||||
|     /// </summary> | ||||
| @@ -183,6 +260,48 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加队列,超限后会入缓存 | ||||
|     /// </summary> | ||||
|     /// <param name="data"></param> | ||||
|     protected virtual void AddQueuePluginDataModel(CacheDBItem<PluginEventData> data) | ||||
|     { | ||||
|         if (_businessPropertyWithCache.CacheEnable) | ||||
|         { | ||||
|             //检测队列长度,超限存入缓存数据库 | ||||
|             if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount) | ||||
|             { | ||||
|                 List<CacheDBItem<PluginEventData>> list = null; | ||||
|                 lock (_memoryPluginEventDataModelQueue) | ||||
|                 { | ||||
|                     if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount) | ||||
|                     { | ||||
|                         list = _memoryPluginEventDataModelQueue.ToListWithDequeue(); | ||||
|                     } | ||||
|                 } | ||||
|                 AddCache(list); | ||||
|             } | ||||
|         } | ||||
|         if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount) | ||||
|         { | ||||
|             lock (_memoryPluginEventDataModelQueue) | ||||
|             { | ||||
|                 if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount) | ||||
|                 { | ||||
|                     LogMessage?.LogWarning($"{typeof(PluginEventData).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase {_businessPropertyWithCache.QueueMaxCount} or Enable cache"); | ||||
|                     _memoryPluginEventDataModelQueue.Clear(); | ||||
|                     _memoryPluginEventDataModelQueue.Enqueue(data); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             _memoryPluginEventDataModelQueue.Enqueue(data); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取缓存对象,注意每次获取的对象可能不一样,如顺序操作,需固定引用 | ||||
|     /// </summary> | ||||
| @@ -197,6 +316,20 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|         } | ||||
|         return cacheDb; | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 获取缓存对象,注意每次获取的对象可能不一样,如顺序操作,需固定引用 | ||||
|     /// </summary> | ||||
|     protected virtual CacheDB LocalDBCachePluginEventDataModel() | ||||
|     { | ||||
|         var cacheDb = CacheDBUtil.GetCache(typeof(CacheDBItem<PluginEventData>), CurrentDevice.Name.ToString(), $"{CurrentDevice.PluginName}_{typeof(PluginEventData).Name}"); | ||||
|  | ||||
|         if (!LocalDBCachePluginEventDataModelInited) | ||||
|         { | ||||
|             cacheDb.InitDb(); | ||||
|             LocalDBCachePluginEventDataModelInited = true; | ||||
|         } | ||||
|         return cacheDb; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 需实现上传到通道 | ||||
| @@ -206,6 +339,16 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|     /// <returns></returns> | ||||
|     protected abstract ValueTask<OperResult> UpdateAlarmModel(List<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken); | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 需实现上传到通道 | ||||
|     /// </summary> | ||||
|     /// <param name="item"></param> | ||||
|     /// <param name="cancellationToken"></param> | ||||
|     /// <returns></returns> | ||||
|     protected abstract ValueTask<OperResult> UpdatePluginEventDataModel(List<CacheDBItem<PluginEventData>> item, CancellationToken cancellationToken); | ||||
|  | ||||
|  | ||||
|     protected async Task UpdateAlarmModelCache(CancellationToken cancellationToken) | ||||
|     { | ||||
|         if (_businessPropertyWithCache.CacheEnable) | ||||
| @@ -262,10 +405,69 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|  | ||||
|             #endregion //成功上传时,补上传缓存数据 | ||||
|         } | ||||
|     } | ||||
|     protected async Task UpdatePluginEventDataModelCache(CancellationToken cancellationToken) | ||||
|     { | ||||
|         if (_businessPropertyWithCache.CacheEnable) | ||||
|         { | ||||
|             #region //成功上传时,补上传缓存数据 | ||||
|  | ||||
|             if (IsConnected()) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     while (!cancellationToken.IsCancellationRequested) | ||||
|                     { | ||||
|                         //循环获取,固定读最大行数量,执行完成需删除行 | ||||
|                         var varList = await DBCachePluginEventData.DBProvider.Queryable<CacheDBItem<PluginEventData>>().Take(_businessPropertyWithCache.SplitSize).ToListAsync(cancellationToken).ConfigureAwait(false); | ||||
|                         if (varList.Count != 0) | ||||
|                         { | ||||
|                             try | ||||
|                             { | ||||
|                                 if (!cancellationToken.IsCancellationRequested) | ||||
|                                 { | ||||
|                                     var result = await UpdatePluginEventDataModel(varList, cancellationToken).ConfigureAwait(false); | ||||
|                                     if (result.IsSuccess) | ||||
|                                     { | ||||
|                                         //删除缓存 | ||||
|                                         await DBCachePluginEventData.DBProvider.Deleteable<CacheDBItem<PluginEventData>>(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 //上传设备内存队列中的数据 | ||||
| @@ -307,6 +509,47 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|  | ||||
|         #endregion //上传设备内存队列中的数据 | ||||
|     } | ||||
|     protected async Task UpdatePluginEventDataModelMemory(CancellationToken cancellationToken) | ||||
|     { | ||||
|         #region //上传设备内存队列中的数据 | ||||
|  | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             var list = _memoryPluginEventDataModelQueue.ToListWithDequeue().ChunkBetter(_businessPropertyWithCache.SplitSize); | ||||
|             foreach (var item in list) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     if (!cancellationToken.IsCancellationRequested) | ||||
|                     { | ||||
|                         var result = await UpdatePluginEventDataModel(item, cancellationToken).ConfigureAwait(false); | ||||
|                         if (!result.IsSuccess) | ||||
|                         { | ||||
|                             AddCache(item); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     if (success) | ||||
|                         LogMessage?.LogWarning(ex); | ||||
|                     success = false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             if (success) | ||||
|                 LogMessage?.LogWarning(ex); | ||||
|             success = false; | ||||
|         } | ||||
|         #endregion //上传设备内存队列中的数据 | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
|   | ||||
| @@ -20,6 +20,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache | ||||
| { | ||||
| #if !Management | ||||
|     protected override bool PluginEventDataModelEnable => true; | ||||
|     protected override bool AlarmModelEnable => true; | ||||
|  | ||||
|     protected override bool DevModelEnable => false; | ||||
| @@ -57,14 +58,40 @@ public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache | ||||
|         GlobalData.AlarmChangedEvent -= AlarmValueChange; | ||||
|         GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a => AlarmValueChange(a.Value)); | ||||
|         GlobalData.AlarmChangedEvent += AlarmValueChange; | ||||
|         GlobalData.PluginEventHandler -= PluginEventChange; | ||||
|         GlobalData.PluginEventHandler += PluginEventChange; | ||||
|  | ||||
|         await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false); | ||||
|     } | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         GlobalData.AlarmChangedEvent -= AlarmValueChange; | ||||
|         GlobalData.PluginEventHandler -= PluginEventChange; | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private void PluginEventChange(PluginEventData value) | ||||
|     { | ||||
|         if (CurrentDevice?.Pause != false) | ||||
|             return; | ||||
|         if (TaskSchedulerLoop?.Stoped == true) return; | ||||
|  | ||||
|         if (!PluginEventDataModelEnable) return; | ||||
|         // 如果业务属性的缓存为间隔上传,则不执行后续操作 | ||||
|         //if (_businessPropertyWithCacheInterval?.IsInterval != true) | ||||
|         { | ||||
|             PluginChange(value); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueuePluginDataModel(CacheDBItem{PluginEventData})"/> 方法。 | ||||
|     /// </summary> | ||||
|     protected virtual void PluginChange(PluginEventData value) | ||||
|     { | ||||
|         // 在报警状态变化时执行的自定义逻辑 | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。 | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -40,7 +40,13 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | ||||
|             GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a => AlarmValueChange(a.Value)); | ||||
|  | ||||
|             GlobalData.AlarmChangedEvent += AlarmValueChange; | ||||
|             // 解绑全局数据的事件 | ||||
|  | ||||
|         } | ||||
|         if (PluginEventDataModelEnable) | ||||
|         { | ||||
|             GlobalData.PluginEventHandler -= PluginEventChange; | ||||
|             GlobalData.PluginEventHandler += PluginEventChange; | ||||
|  | ||||
|         } | ||||
|         if (DevModelEnable) | ||||
|         { | ||||
| @@ -64,7 +70,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | ||||
|     } | ||||
|     public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         if (AlarmModelEnable || DevModelEnable || VarModelEnable) | ||||
|         if (AlarmModelEnable || DevModelEnable || VarModelEnable || PluginEventDataModelEnable) | ||||
|         { | ||||
|             // 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表 | ||||
|             if (_businessPropertyWithCacheInterval.IsAllVariable) | ||||
| @@ -137,14 +143,12 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | ||||
|     { | ||||
|         // 解绑事件 | ||||
|         GlobalData.AlarmChangedEvent -= AlarmValueChange; | ||||
|         GlobalData.PluginEventHandler -= PluginEventChange; | ||||
|  | ||||
|         GlobalData.VariableValueChangeEvent -= VariableValueChange; | ||||
|         GlobalData.DeviceStatusChangeEvent -= DeviceStatusChange; | ||||
|  | ||||
|         // 清空内存队列 | ||||
|         _memoryAlarmModelQueue.Clear(); | ||||
|         _memoryDevModelQueue.Clear(); | ||||
|         _memoryVarModelQueue.Clear(); | ||||
|         _memoryVarModelsQueue.Clear(); | ||||
|  | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
| @@ -226,7 +230,26 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | ||||
|     { | ||||
|         // 在变量状态变化时执行的自定义逻辑 | ||||
|     } | ||||
|     private void PluginEventChange(PluginEventData value) | ||||
|     { | ||||
|         if (CurrentDevice?.Pause != false) | ||||
|             return; | ||||
|         if (TaskSchedulerLoop?.Stoped == true) return; | ||||
|  | ||||
|         if (!PluginEventDataModelEnable) return; | ||||
|         // 如果业务属性的缓存为间隔上传,则不执行后续操作 | ||||
|         //if (_businessPropertyWithCacheInterval?.IsInterval != true) | ||||
|         { | ||||
|             PluginChange(value); | ||||
|         } | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueAlarmModel"/> 方法。 | ||||
|     /// </summary> | ||||
|     protected virtual void PluginChange(PluginEventData value) | ||||
|     { | ||||
|         // 在报警状态变化时执行的自定义逻辑 | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。 | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -26,8 +26,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|  | ||||
|     protected abstract BusinessPropertyWithCacheIntervalScript _businessPropertyWithCacheIntervalScript { get; } | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|  | ||||
| #if !Management | ||||
|     protected internal override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken) | ||||
|     { | ||||
|         CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel); | ||||
|         CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel); | ||||
|         CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptPluginEventDataModel); | ||||
|         CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel); | ||||
|         return base.InitChannelAsync(channel, cancellationToken); | ||||
|     } | ||||
|     public virtual string[] Match(string input) | ||||
|     { | ||||
|         // 生成缓存键,以确保缓存的唯一性 | ||||
| @@ -64,7 +73,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|  | ||||
|     protected IEnumerable<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmVariable> item) | ||||
|     { | ||||
|         var data = Application.DynamicModelExtension.GetDynamicModel<AlarmVariable>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel); | ||||
|         var data = Application.DynamicModelExtension.GetDynamicModel<AlarmVariable>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel, LogMessage); | ||||
|         var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic); | ||||
|         if (topics.Length > 0) | ||||
|         { | ||||
| @@ -88,16 +97,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|                     if (_businessPropertyWithCacheIntervalScript.IsAlarmList) | ||||
|                     { | ||||
|                         var gList = group.ToList(); | ||||
|                         var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                         if (gList.Count > 0) | ||||
|                         { | ||||
|                             var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                             // 将主题和 JSON 内容添加到列表中 | ||||
|                             yield return new(topic, json, gList.Count); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         // 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串 | ||||
|                         foreach (var gro in group) | ||||
|                         { | ||||
|                             var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                             var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                             // 将主题和 JSON 内容添加到列表中 | ||||
|                             yield return new(topic, json, 1); | ||||
|                         } | ||||
| @@ -110,23 +122,92 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|             if (_businessPropertyWithCacheIntervalScript.IsAlarmList) | ||||
|             { | ||||
|                 var gList = data.ToList(); | ||||
|                 var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                 if (gList.Count > 0) | ||||
|                 { | ||||
|                     var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 foreach (var group in data) | ||||
|                 { | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     protected IEnumerable<TopicArray> GetPluginEventDataTopicArrays(IEnumerable<PluginEventData> item) | ||||
|     { | ||||
|         var data = Application.DynamicModelExtension.GetDynamicModel<PluginEventData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptPluginEventDataModel, LogMessage); | ||||
|         var topics = Match(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic); | ||||
|         if (topics.Length > 0) | ||||
|         { | ||||
|             { | ||||
|                 //获取分组最终结果 | ||||
|                 var groups = data.GroupByKeys(topics); | ||||
|  | ||||
|                 foreach (var group in groups) | ||||
|                 { | ||||
|                     // 上传主题 | ||||
|                     // 获取预定义的报警主题 | ||||
|                     string topic = _businessPropertyWithCacheIntervalScript.PluginEventDataTopic; | ||||
|  | ||||
|                     // 将主题中的占位符替换为分组键对应的值 | ||||
|                     for (int i = 0; i < topics.Length; i++) | ||||
|                     { | ||||
|                         topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString()); | ||||
|                     } | ||||
|  | ||||
|                     // 上传内容 | ||||
|                     if (_businessPropertyWithCacheIntervalScript.IsPluginEventDataList) | ||||
|                     { | ||||
|                         var gList = group.ToList(); | ||||
|                         if (gList.Count > 0) | ||||
|                         { | ||||
|                             var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                             // 将主题和 JSON 内容添加到列表中 | ||||
|                             yield return new(topic, json, gList.Count); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         // 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串 | ||||
|                         foreach (var gro in group) | ||||
|                         { | ||||
|                             var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                             // 将主题和 JSON 内容添加到列表中 | ||||
|                             yield return new(topic, json, 1); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (_businessPropertyWithCacheIntervalScript.IsPluginEventDataList) | ||||
|             { | ||||
|                 var gList = data.ToList(); | ||||
|                 if (gList.Count > 0) | ||||
|                 { | ||||
|                     var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic, json, gList.Count); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 foreach (var group in data) | ||||
|                 { | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic, json, 1); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     protected IEnumerable<TopicArray> GetDeviceTopicArray(IEnumerable<DeviceBasicData> item) | ||||
|     { | ||||
|         var data = Application.DynamicModelExtension.GetDynamicModel<DeviceBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel); | ||||
|         var data = Application.DynamicModelExtension.GetDynamicModel<DeviceBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel, LogMessage); | ||||
|         var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic); | ||||
|         if (topics.Length > 0) | ||||
|         { | ||||
| @@ -152,16 +233,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|                         { | ||||
|                             // 如果是设备列表,则将整个分组转换为 JSON 字符串 | ||||
|                             var gList = group.Select(a => a).ToList(); | ||||
|                             var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                             if (gList.Count > 0) | ||||
|                             { | ||||
|                                 var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                                 // 将主题和 JSON 内容添加到列表中 | ||||
|                                 yield return new(topic, json, gList.Count); | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             // 如果不是设备列表,则将每个分组元素分别转换为 JSON 字符串 | ||||
|                             foreach (var gro in group) | ||||
|                             { | ||||
|                                 var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                                 var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                                 // 将主题和 JSON 内容添加到列表中 | ||||
|                                 yield return new(topic, json, 1); | ||||
|                             } | ||||
| @@ -175,14 +259,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|             if (_businessPropertyWithCacheIntervalScript.IsDeviceList) | ||||
|             { | ||||
|                 var gList = data.ToList(); | ||||
|                 var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                 if (gList.Count > 0) | ||||
|                 { | ||||
|                     var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 foreach (var group in data) | ||||
|                 { | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1); | ||||
|                 } | ||||
|             } | ||||
| @@ -191,7 +278,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|  | ||||
|     protected IEnumerable<TopicArray> GetVariableScriptTopicArray(IEnumerable<VariableBasicData> item) | ||||
|     { | ||||
|         var data = Application.DynamicModelExtension.GetDynamicModel<VariableBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel); | ||||
|         var data = Application.DynamicModelExtension.GetDynamicModel<VariableBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel, LogMessage); | ||||
|         var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic); | ||||
|         if (topics.Length > 0) | ||||
|         { | ||||
| @@ -217,16 +304,20 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|                         { | ||||
|                             // 如果是变量列表,则将整个分组转换为 JSON 字符串 | ||||
|                             var gList = group.Select(a => a).ToList(); | ||||
|                             var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|  | ||||
|                             if (gList.Count > 0) | ||||
|                             { | ||||
|                                 var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                                 // 将主题和 JSON 内容添加到列表中 | ||||
|                                 yield return new(topic, json, gList.Count); | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             // 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串 | ||||
|                             foreach (var gro in group) | ||||
|                             { | ||||
|                                 var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                                 var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                                 // 将主题和 JSON 内容添加到列表中 | ||||
|                                 yield return new(topic, json, 1); | ||||
|                             } | ||||
| @@ -240,14 +331,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|             if (_businessPropertyWithCacheIntervalScript.IsVariableList) | ||||
|             { | ||||
|                 var gList = data.ToList(); | ||||
|                 var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                 if (gList.Count > 0) | ||||
|                 { | ||||
|                     var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 foreach (var group in data) | ||||
|                 { | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1); | ||||
|                 } | ||||
|             } | ||||
| @@ -294,16 +388,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|                         { | ||||
|                             // 如果是变量列表,则将整个分组转换为 JSON 字符串 | ||||
|                             var gList = group.Select(a => a).ToList(); | ||||
|                             var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                             if (gList.Count > 0) | ||||
|                             { | ||||
|                                 var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                                 // 将主题和 JSON 内容添加到列表中 | ||||
|                                 yield return new(topic, json, gList.Count); | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             // 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串 | ||||
|                             foreach (var gro in group) | ||||
|                             { | ||||
|                                 var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                                 var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                                 // 将主题和 JSON 内容添加到列表中 | ||||
|                                 yield return new(topic, json, 1); | ||||
|                             } | ||||
| @@ -317,14 +414,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|             if (_businessPropertyWithCacheIntervalScript.IsVariableList) | ||||
|             { | ||||
|                 var gList = data.ToList(); | ||||
|                 var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                 if (gList.Count > 0) | ||||
|                 { | ||||
|                     var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 foreach (var group in data) | ||||
|                 { | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented); | ||||
|                     var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull); | ||||
|                     yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1); | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -16,6 +16,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| public abstract partial class BusinessBaseWithCacheIntervalScriptAll : BusinessBaseWithCacheIntervalScript | ||||
| { | ||||
| #if !Management | ||||
|     protected override bool PluginEventDataModelEnable => true; | ||||
|     protected override bool AlarmModelEnable => true; | ||||
|  | ||||
|     protected override bool DevModelEnable => true; | ||||
|   | ||||
| @@ -16,6 +16,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCacheInterval | ||||
| { | ||||
| #if !Management | ||||
|     protected override bool PluginEventDataModelEnable => false; | ||||
|     protected override bool AlarmModelEnable => false; | ||||
|  | ||||
|     protected override bool DevModelEnable => false; | ||||
| @@ -30,5 +31,9 @@ public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCa | ||||
|     { | ||||
|         throw new NotImplementedException(); | ||||
|     } | ||||
|     protected override ValueTask<OperResult> UpdatePluginEventDataModel(List<CacheDBItem<PluginEventData>> item, CancellationToken cancellationToken) | ||||
|     { | ||||
|         throw new NotImplementedException(); | ||||
|     } | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache | ||||
|     /// </summary> | ||||
|     [DynamicProperty] | ||||
|     public bool JsonFormattingIndented { get; set; } = true; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 忽略Null值 | ||||
|     /// </summary> | ||||
|     [DynamicProperty] | ||||
|     public bool JsonIgnoreNull { get; set; } = true; | ||||
|     /// <summary> | ||||
|     /// 设备Topic | ||||
|     /// </summary> | ||||
| @@ -46,6 +50,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache | ||||
|     /// </summary> | ||||
|     [DynamicProperty] | ||||
|     public bool IsAlarmList { get; set; } = true; | ||||
|     /// <summary> | ||||
|     /// 报警Topic | ||||
|     /// </summary> | ||||
|     [DynamicProperty] | ||||
|     public bool IsPluginEventDataList { get; set; } = true; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备Topic | ||||
| @@ -65,6 +74,12 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache | ||||
|     [DynamicProperty(Remark = "可使用${key}作为匹配项,key必须是上传实体中的属性,比如ThingsGateway/Alarm/${DeviceName}")] | ||||
|     public string AlarmTopic { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 报警Topic | ||||
|     /// </summary> | ||||
|     [DynamicProperty(Remark = "可使用${key}作为匹配项,key必须是上传实体中的属性,比如ThingsGateway/PluginEventData/${DeviceName}")] | ||||
|     public string PluginEventDataTopic { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备实体脚本 | ||||
|     /// </summary> | ||||
| @@ -85,4 +100,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache | ||||
|     [DynamicProperty] | ||||
|     [AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)] | ||||
|     public string? BigTextScriptAlarmModel { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 报警实体脚本 | ||||
|     /// </summary> | ||||
|     [DynamicProperty] | ||||
|     [AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)] | ||||
|     public string? BigTextScriptPluginEventDataModel { get; set; } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,37 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://thingsgateway.cn/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| /// <summary> | ||||
| /// <inheritdoc/> | ||||
| /// </summary> | ||||
| public class BusinessVariableProperty : VariablePropertyBase | ||||
| { | ||||
|     [DynamicProperty] | ||||
|     public string Data1 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data2 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data3 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data4 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data5 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data6 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data7 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data8 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data9 { get; set; } | ||||
|     [DynamicProperty] | ||||
|     public string Data10 { get; set; } | ||||
| } | ||||
| @@ -366,8 +366,17 @@ public abstract partial class CollectBase : DriverBase | ||||
|  | ||||
|         if (Pause) return; | ||||
|         if (cancellationToken.IsCancellationRequested) return; | ||||
|         CancellationToken readToken = default; | ||||
|         var readerLockTask = ReadWriteLock.ReaderLockAsync(cancellationToken); | ||||
|         if (!readerLockTask.IsCompleted) | ||||
|         { | ||||
|             readToken = await readerLockTask.ConfigureAwait(false); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             readToken = readerLockTask.Result; | ||||
|         } | ||||
|  | ||||
|         var readToken = await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false); | ||||
|         if (readToken.IsCancellationRequested) | ||||
|         { | ||||
|             await ReadVariableSource(state, cancellationToken).ConfigureAwait(false); | ||||
| @@ -379,7 +388,17 @@ public abstract partial class CollectBase : DriverBase | ||||
|  | ||||
|         //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||
|         //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length)); | ||||
|         var readResult = await ReadSourceAsync(variableSourceRead, allToken).ConfigureAwait(false); | ||||
|  | ||||
|         OperResult<ReadOnlyMemory<byte>> readResult = default; | ||||
|         var readTask = ReadSourceAsync(variableSourceRead, allToken); | ||||
|         if (!readTask.IsCompleted) | ||||
|         { | ||||
|             readResult = await readTask.ConfigureAwait(false); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             readResult = readTask.Result; | ||||
|         } | ||||
|  | ||||
|         var readErrorCount = 0; | ||||
|  | ||||
| @@ -403,7 +422,16 @@ public abstract partial class CollectBase : DriverBase | ||||
|  | ||||
|             //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||
|             //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length)); | ||||
|             readResult = await ReadSourceAsync(variableSourceRead, allToken).ConfigureAwait(false); | ||||
|             var readTask1 = ReadSourceAsync(variableSourceRead, allToken); | ||||
|             if (!readTask1.IsCompleted) | ||||
|             { | ||||
|                 readResult = await readTask1.ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 readResult = readTask1.Result; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         if (readResult.IsSuccess) | ||||
|   | ||||
| @@ -159,7 +159,16 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|                 return new(new OperationCanceledException()); | ||||
|  | ||||
|             // 从协议读取数据 | ||||
|             var read = await FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken).ConfigureAwait(false); | ||||
|             OperResult<ReadOnlyMemory<byte>> read = default; | ||||
|             var readTask = FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken); | ||||
|             if (!readTask.IsCompleted) | ||||
|             { | ||||
|                 read = await readTask.ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 read = readTask.Result; | ||||
|             } | ||||
|  | ||||
|             // 如果读取成功且有有效内容,则解析结构化内容 | ||||
|             if (read.IsSuccess) | ||||
|   | ||||
| @@ -23,12 +23,16 @@ public static class DynamicModelExtension | ||||
|     /// <summary> | ||||
|     /// GetDynamicModel | ||||
|     /// </summary> | ||||
|     public static IEnumerable<object> GetDynamicModel<T>(this IEnumerable<T> datas, string script) | ||||
|     public static IEnumerable<object> GetDynamicModel<T>(this IEnumerable<T> datas, string script, TouchSocket.Core.ILog log) | ||||
|     { | ||||
|         if (!string.IsNullOrEmpty(script)) | ||||
|         { | ||||
|             //执行脚本,获取新实体 | ||||
|             var getDeviceModel = CSharpScriptEngineExtension.Do<IDynamicModel>(script); | ||||
|             if (getDeviceModel is DynamicModelBase dynamicModelBase) | ||||
|             { | ||||
|                 dynamicModelBase.Logger = log; | ||||
|             } | ||||
|             return getDeviceModel.GetList(datas?.Cast<object>()); | ||||
|         } | ||||
|         else | ||||
| @@ -50,9 +54,17 @@ public static class DynamicModelExtension | ||||
|         if (variableRuntime == null || propertyName.IsNullOrWhiteSpace()) | ||||
|             return null; | ||||
|  | ||||
|         // 检查是否存在对应的业务设备Id | ||||
|         if (variableRuntime.VariablePropertys?.TryGetValue(businessId, out var keyValuePairs) == true) | ||||
|         { | ||||
|             keyValuePairs.TryGetValue(propertyName, out var value); | ||||
|             return value; // 返回属性值 | ||||
|         } | ||||
|  | ||||
|  | ||||
|         if (GlobalData.IdDevices.TryGetValue(businessId, out var deviceRuntime)) | ||||
|         { | ||||
|             if (deviceRuntime.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property) | ||||
|             if (deviceRuntime.Driver is BusinessBase businessBase && businessBase.DriverProperties is IBusinessPropertyAllVariableBase property) | ||||
|             { | ||||
|                 if (property.IsAllVariable) | ||||
|                 { | ||||
| @@ -64,18 +76,12 @@ public static class DynamicModelExtension | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         return ThingsGatewayStringConverter.Default.Serialize(null, property.GetValue(propertyName, false)); | ||||
|                         return ThingsGatewayStringConverter.Default.Serialize(null, businessBase.VariablePropertys.GetValue(propertyName, false)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 检查是否存在对应的业务设备Id | ||||
|         if (variableRuntime.VariablePropertys?.ContainsKey(businessId) == true) | ||||
|         { | ||||
|             variableRuntime.VariablePropertys[businessId].TryGetValue(propertyName, out var value); | ||||
|             return value; // 返回属性值 | ||||
|         } | ||||
|  | ||||
|         return null; // 未找到对应的业务设备Id,返回null | ||||
|     } | ||||
| @@ -127,3 +133,8 @@ public interface IDynamicModel | ||||
| { | ||||
|     IEnumerable<dynamic> GetList(IEnumerable<object> datas); | ||||
| } | ||||
| public abstract class DynamicModelBase : IDynamicModel | ||||
| { | ||||
|     public TouchSocket.Core.ILog Logger { get; set; } | ||||
|     public abstract IEnumerable<dynamic> GetList(IEnumerable<object> datas); | ||||
| } | ||||
|   | ||||
| @@ -40,6 +40,28 @@ public delegate void VariableCollectEventHandler(VariableRuntime variableRuntime | ||||
| /// </summary> | ||||
| public delegate void VariableAlarmEventHandler(AlarmVariable alarmVariable); | ||||
|  | ||||
| public delegate void PluginEventHandler(PluginEventData data); | ||||
|  | ||||
| public class PluginEventData | ||||
| { | ||||
|     public string DeviceName { get; set; } | ||||
|     public Newtonsoft.Json.Linq.JObject Value { get; set; } | ||||
|     public string ValueType { get; set; } | ||||
|  | ||||
|     [System.Text.Json.Serialization.JsonIgnore] | ||||
|     [Newtonsoft.Json.JsonIgnore] | ||||
|     public object ObjectValue { get; set; } | ||||
|     //public object ObjectValue => ValueType.IsNullOrEmpty() ? Value : Value.ToObject(Type.GetType(ValueType, false) ?? typeof(JObject)); | ||||
|  | ||||
|     public PluginEventData(string deviceName, object value) | ||||
|     { | ||||
|         DeviceName = deviceName; | ||||
|         ObjectValue = value; | ||||
|         Value = Newtonsoft.Json.Linq.JObject.FromObject(value); | ||||
|         ValueType = $"{value?.GetType().FullName},{value?.GetType().Assembly.GetName().Name}"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// 采集设备值与状态全局提供类,用于提供全局的设备状态和变量数据的管理 | ||||
| /// </summary> | ||||
| @@ -64,6 +86,8 @@ public static class GlobalData | ||||
|     /// </summary> | ||||
|     public static event VariableAlarmEventHandler? AlarmChangedEvent; | ||||
|  | ||||
|     public static event PluginEventHandler? PluginEventHandler; | ||||
|  | ||||
|     public static async Task<IEnumerable<ChannelRuntime>> GetCurrentUserChannels() | ||||
|     { | ||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||
| @@ -480,6 +504,14 @@ public static class GlobalData | ||||
|         AlarmChangedEvent?.Invoke(alarmVariable); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 事件状态变化处理方法,用于处理事件状态变化时的逻辑 | ||||
|     /// </summary> | ||||
|     public static void PluginEventChange(PluginEventData pluginEventData) | ||||
|     { | ||||
|         // 触发设备状态变化事件,并将设备运行时对象转换为设备数据对象进行传递 | ||||
|         PluginEventHandler?.Invoke(pluginEventData); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 设备状态变化处理方法,用于处理设备状态变化时的逻辑 | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -61,7 +61,7 @@ public class LogJob : IJob | ||||
|  | ||||
|         //网关通道日志以通道id命名 | ||||
|         var rulesService = App.RootServices.GetService<IRulesService>(); | ||||
|         var ruleNames = (await rulesService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet(); | ||||
|         var ruleNames = (await rulesService.GetAllRulesAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet(); | ||||
|         var ruleBaseDir = RulesEngineHostedService.LogDir; | ||||
|         Directory.CreateDirectory(ruleBaseDir); | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,21 @@ | ||||
| { | ||||
|  | ||||
|   "ThingsGateway.Gateway.Application.BusinessVariableProperty": { | ||||
|  | ||||
|     "Data1": "Data1", | ||||
|     "Data2": "Data2", | ||||
|     "Data3": "Data3", | ||||
|     "Data4": "Data4", | ||||
|     "Data5": "Data5", | ||||
|     "Data6": "Data6", | ||||
|     "Data7": "Data7", | ||||
|     "Data8": "Data8", | ||||
|     "Data9": "Data9", | ||||
|     "Data10": "Data10" | ||||
|   }, | ||||
|  | ||||
|  | ||||
|  | ||||
|   "ThingsGateway.Management.Application.ManagementExportString": { | ||||
|  | ||||
|     "ManagementConfigName": "ManagementConfigName" | ||||
| @@ -30,6 +47,7 @@ | ||||
|  | ||||
|     "Actuator": "Actuator", | ||||
|     "AlarmChangedTriggerNode": "AlarmStateTrigger", | ||||
|     "PluginEventChangedTriggerNode": "PluginEventTrigger", | ||||
|     "BusinessNode": "BusinessDeviceExecution", | ||||
|     "BusinessNode.Placeholder": "BusinessDeviceName", | ||||
|     "Cancel": "Cancel", | ||||
| @@ -225,15 +243,19 @@ | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.BusinessPropertyWithCacheIntervalScript": { | ||||
|     "AlarmTopic": "AlarmTopic", | ||||
|     "PluginEventDataTopic": "PluginEventDataTopic", | ||||
|     "BigTextScriptAlarmModel": "BigTextScriptAlarmModel", | ||||
|     "BigTextScriptPluginEventDataModel": "BigTextScriptPluginEventDataModel", | ||||
|     "BigTextScriptDeviceModel": "BigTextScriptDeviceModel", | ||||
|     "BigTextScriptVariableModel": "BigTextScriptVariableModel", | ||||
|     "DetailLog": "DetailLog", | ||||
|     "DeviceTopic": "DeviceTopic", | ||||
|     "IsAlarmList": "IsAlarmList", | ||||
|     "IsPluginEventDataList": "IsPluginEventDataList", | ||||
|     "IsDeviceList": "IsDeviceList", | ||||
|     "IsVariableList": "IsVariableList", | ||||
|     "JsonFormattingIndented": "JsonFormattingIndented", | ||||
|     "JsonIgnoreNull": "JsonIgnoreNull", | ||||
|     "VariableTopic": "VariableTopic" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.BusinessUpdateEnum": { | ||||
|   | ||||
| @@ -1,4 +1,18 @@ | ||||
| { | ||||
|   "ThingsGateway.Gateway.Application.BusinessVariableProperty": { | ||||
|  | ||||
|     "Data1": "备注1", | ||||
|     "Data2": "备注2", | ||||
|     "Data3": "备注3", | ||||
|     "Data4": "备注4", | ||||
|     "Data5": "备注5", | ||||
|     "Data6": "备注6", | ||||
|     "Data7": "备注7", | ||||
|     "Data8": "备注8", | ||||
|     "Data9": "备注9", | ||||
|     "Data10": "备注10" | ||||
|   }, | ||||
|  | ||||
|   "ThingsGateway.Management.Application.ManagementExportString": { | ||||
|  | ||||
|     "ManagementConfigName": "通讯配置" | ||||
| @@ -32,6 +46,7 @@ | ||||
|  | ||||
|     "Actuator": "执行", | ||||
|     "AlarmChangedTriggerNode": "报警状态触发器", | ||||
|     "PluginEventChangedTriggerNode": "插件事件触发器", | ||||
|     "BusinessNode": "业务设备执行", | ||||
|     "BusinessNode.Placeholder": "设备名称", | ||||
|     "Cancel": "取消", | ||||
| @@ -227,15 +242,19 @@ | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.BusinessPropertyWithCacheIntervalScript": { | ||||
|     "AlarmTopic": "报警主题", | ||||
|     "PluginEventDataTopic": "插件事件主题", | ||||
|     "BigTextScriptAlarmModel": "报警上传脚本", | ||||
|     "BigTextScriptPluginEventDataModel": "插件事件上传脚本", | ||||
|     "BigTextScriptDeviceModel": "设备上传脚本", | ||||
|     "BigTextScriptVariableModel": "变量上传脚本", | ||||
|     "DetailLog": "详细日志", | ||||
|     "DeviceTopic": "设备主题", | ||||
|     "IsAlarmList": "报警列表上传", | ||||
|     "IsPluginEventDataList": "插件事件列表上传", | ||||
|     "IsDeviceList": "设备状态列表上传", | ||||
|     "IsVariableList": "变量列表上传", | ||||
|     "JsonFormattingIndented": "Json缩进格式化", | ||||
|     "JsonIgnoreNull": "Json忽略null值", | ||||
|     "VariableTopic": "变量主题" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.BusinessUpdateEnum": { | ||||
|   | ||||
| @@ -87,6 +87,12 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public async Task RestartChannelsAsync() | ||||
|     { | ||||
|         var data = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false); | ||||
|         await RestartChannelAsync(data.ToList()).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     public async Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel) | ||||
|     { | ||||
|         if (GlobalData.IdChannels.TryGetValue(id, out var ChannelRuntime)) | ||||
|   | ||||
| @@ -20,6 +20,7 @@ namespace ThingsGateway.Gateway.Application | ||||
|         Task<string> GetPluginNameAsync(long channelId); | ||||
|  | ||||
|         Task RestartChannelAsync(long channelId); | ||||
|         Task RestartChannelsAsync(); | ||||
|  | ||||
|         Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id); | ||||
|         Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|   | ||||
| @@ -34,12 +34,15 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|  | ||||
|         return Task.FromResult(GlobalData.ReadOnlyIdDevices.ToDictionary(a => a.Key, a => Tuple.Create(a.Value.Name, a.Value.PluginName))); | ||||
|     } | ||||
|  | ||||
|     public async Task<List<SelectedItem>> GetDeviceItemsAsync(bool isCollect) | ||||
|     public async Task<QueryData<SelectedItem>> OnDeviceSelectedItemQueryAsync(VirtualizeQueryOption option, bool isCollect) | ||||
|     { | ||||
|         var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false); | ||||
|         return devices.Where(a => a.IsCollect == isCollect).BuildDeviceSelectList().ToList(); | ||||
|  | ||||
|         return devices.Where(a => a.IsCollect == isCollect).WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(option.SearchText)).GetQueryData(option, GatewayResourceUtil.BuildDeviceSelectList); | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public Task<string> GetDevicePluginNameAsync(long id) | ||||
|     { | ||||
|         return Task.FromResult(GlobalData.ReadOnlyIdDevices.TryGetValue(id, out var deviceRuntime) ? deviceRuntime.PluginName : string.Empty); | ||||
|   | ||||
| @@ -42,7 +42,7 @@ namespace ThingsGateway.Gateway.Application | ||||
|         Task<bool> ClearDeviceAsync(bool restart); | ||||
|         Task<bool> IsRedundantDeviceAsync(long id); | ||||
|         Task<string> GetDeviceNameAsync(long redundantDeviceId); | ||||
|         Task<List<SelectedItem>> GetDeviceItemsAsync(bool isCollect); | ||||
|         Task<QueryData<SelectedItem>> OnDeviceSelectedItemQueryAsync(VirtualizeQueryOption option, bool isCollect); | ||||
|         Task<string> GetDevicePluginNameAsync(long id); | ||||
|  | ||||
|         Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync(); | ||||
|   | ||||
| @@ -14,27 +14,86 @@ using ThingsGateway.Authentication; | ||||
|  | ||||
| using TouchSocket.Dmtp.Rpc; | ||||
| using TouchSocket.Rpc; | ||||
| using TouchSocket.WebApi; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| #if Management | ||||
| [GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)] | ||||
| #endif | ||||
| [TouchSocket.WebApi.Router("/miniapi/managementrpc/[action]")] | ||||
| public interface IManagementRpcServer : IRpcServer | ||||
| { | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, Dictionary<string, OperResult<object>>>> RpcAsync(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task DeleteBackendLogAsync(); | ||||
|     [DmtpRpc] | ||||
|     Task<List<BackendLog>> GetNewBackendLogAsync(); | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> BatchEditChannelAsync(List<Channel> models, Channel oldModel, Channel model, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> BatchEditDeviceAsync(List<Device> models, Device oldModel, Device model, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> BatchEditVariableAsync(List<Variable> models, Variable oldModel, Variable model, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> BatchSaveVariableAsync(List<Variable> input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> ClearChannelAsync(bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> ClearDeviceAsync(bool restart); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 清除所有规则 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task ClearRulesAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> ClearVariableAsync(bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task CopyChannelAsync(int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long channelId, bool AutoRestartThread); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task CopyVariableAsync(List<Variable> Model, int CopyCount, string CopyVariableNamePrefix, int CopyVariableNameSuffixNumber, bool AutoRestartThread); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task DeleteBackendLogAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> DeleteChannelAsync(List<long> ids, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> DeleteDeviceAsync(List<long> ids, bool restart); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 删除 RpcLog 表中的所有记录 | ||||
| @@ -43,21 +102,281 @@ public interface IManagementRpcServer : IRpcServer | ||||
|     /// 调用此方法会删除 RpcLog 表中的所有记录。 | ||||
|     /// </remarks> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task DeleteRpcLogAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task DeleteRuleRuntimesAsync(List<long> ids); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 删除规则 | ||||
|     /// </summary> | ||||
|     /// <param name="ids">待删除规则的ID列表</param> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> DeleteRulesAsync(List<long> ids); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> DeleteVariableAsync(List<long> ids, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<TouchSocket.Core.LogLevel> DeviceLogLevelAsync(long id); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task DeviceRedundantThreadAsync(long id); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 修改冗余设置 | ||||
|     /// </summary> | ||||
|     /// <param name="input"></param> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task EditRedundancyOptionAsync(RedundancyOptions input); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task EditRuleRuntimesAsync(Rules rules); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<USheetDatas> ExportChannelAsync(List<Channel> channels); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<USheetDatas> ExportDeviceAsync(List<Device> devices); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<string> ExportDeviceFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<USheetDatas> ExportVariableAsync(List<Variable> models, string? sortName, SortOrder sortOrder); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<string> ExportVariableFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 从缓存/数据库获取全部信息 | ||||
|     /// </summary> | ||||
|     /// <returns>规则列表</returns> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<List<Rules>> GetAllRulesAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<List<Channel>> GetChannelListAsync(QueryPageOptions options, int max = 0); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<string> GetChannelNameAsync(long channelId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<IEnumerable<SelectedItem>> GetCurrentUserDeviceSelectedItemsAsync(string searchText, int startIndex, int count); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<QueryData<SelectedItem>> GetCurrentUserDeviceVariableSelectedItemsAsync(string deviceText, string searchText, int startIndex, int count); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<List<Device>> GetDeviceListAsync(QueryPageOptions option, int v); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<string> GetDeviceNameAsync(long redundantDeviceId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<string> GetDevicePluginNameAsync(long id); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<List<BackendLog>> GetNewBackendLogAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取最新的十条 RpcLog 记录 | ||||
|     /// </summary> | ||||
|     /// <returns>最新的十条记录</returns> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<List<RpcLog>> GetNewRpcLogAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<string> GetPluginNameAsync(long channelId); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 根据插件类型获取信息 | ||||
|     /// </summary> | ||||
|     /// <param name="pluginType"></param> | ||||
|     /// <returns></returns> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<List<PluginInfo>> GetPluginsAsync(PluginTypeEnum? pluginType = null); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取冗余设置 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<RedundancyOptions> GetRedundancyAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<Rules> GetRuleRuntimesAsync(long rulesId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<List<Variable>> GetVariableListAsync(QueryPageOptions option, int v); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelFileAsync(string filePath, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync(USheetDatas input, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceFileAsync(string filePath, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync(USheetDatas input, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableFileAsync(string filePath, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableUSheetDatasAsync(USheetDatas data, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<bool> IsRedundantDeviceAsync(long id); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<ChannelRuntime>> OnChannelQueryAsync(QueryPageOptions options); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync(QueryPageOptions options); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<SelectedItem>> OnDeviceSelectedItemQueryAsync(VirtualizeQueryOption option, bool isCollect); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync(VirtualizeQueryOption option, long deviceId, long channelId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<VariableRuntime>> OnVariableQueryAsync(QueryPageOptions options); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<OperResult<object>> OnWriteVariableAsync(long id, string writeData); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task PauseThreadAsync(long id); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 分页显示插件 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<PluginInfo>> PluginPageAsync(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task RedundancyForcedSync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<TouchSocket.Core.LogLevel> RedundancyLogLevelAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<string> RedundancyLogPathAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 重载插件 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task ReloadPluginAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task RestartChannelAsync(long channelId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task RestartChannelsAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task RestartDeviceAsync(long id, bool deleteCache); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task RestartServerAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<Dictionary<string, Dictionary<string, OperResult<object>>>> RpcAsync(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas); | ||||
|     /// <summary> | ||||
|     /// 分页查询 RpcLog 数据 | ||||
|     /// </summary> | ||||
|     /// <param name="option">查询选项</param> | ||||
|     /// <returns>查询到的数据</returns> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option); | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -66,136 +385,15 @@ public interface IManagementRpcServer : IRpcServer | ||||
|     /// <param name="day">统计的天数</param> | ||||
|     /// <returns>按天统计的结果列表</returns> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task RestartServerAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> UUIDAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<AuthorizeInfo> TryAuthorizeAsync(string password); | ||||
|     [DmtpRpc] | ||||
|     Task<AuthorizeInfo> TryGetAuthorizeInfoAsync(); | ||||
|     [DmtpRpc] | ||||
|     Task UnAuthorizeAsync(); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> StartBusinessChannelEnableAsync(); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> StartCollectChannelEnableAsync(); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task StartRedundancyTaskAsync(); | ||||
|     [DmtpRpc] | ||||
|     Task StopRedundancyTaskAsync(); | ||||
|     [DmtpRpc] | ||||
|     Task RedundancyForcedSync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<TouchSocket.Core.LogLevel> RedundancyLogLevelAsync(); | ||||
|     [DmtpRpc] | ||||
|     Task SetRedundancyLogLevelAsync(TouchSocket.Core.LogLevel logLevel); | ||||
|     [DmtpRpc] | ||||
|     Task<string> RedundancyLogPathAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 修改冗余设置 | ||||
|     /// </summary> | ||||
|     /// <param name="input"></param> | ||||
|     [DmtpRpc] | ||||
|     Task EditRedundancyOptionAsync(RedundancyOptions input); | ||||
|     /// <summary> | ||||
|     /// 获取冗余设置 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     Task<RedundancyOptions> GetRedundancyAsync(); | ||||
|     [DmtpRpc] | ||||
|     Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath); | ||||
|     [DmtpRpc] | ||||
|     Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 根据插件类型获取信息 | ||||
|     /// </summary> | ||||
|     /// <param name="pluginType"></param> | ||||
|     /// <returns></returns> | ||||
|     [DmtpRpc] | ||||
|     Task<List<PluginInfo>> GetPluginsAsync(PluginTypeEnum? pluginType = null); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 分页显示插件 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<PluginInfo>> PluginPageAsync(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 重载插件 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     Task ReloadPluginAsync(); | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加插件 | ||||
|     /// </summary> | ||||
|     /// <param name="plugin"></param> | ||||
|     /// <returns></returns> | ||||
|     [DmtpRpc] | ||||
|     Task SavePluginByPathAsync(PluginAddPathInput plugin); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync(); | ||||
|  | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<IEnumerable<SelectedItem>> GetCurrentUserDeviceSelectedItemsAsync(string searchText, int startIndex, int count); | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<SelectedItem>> GetCurrentUserDeviceVariableSelectedItemsAsync(string deviceText, string searchText, int startIndex, int count); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<TouchSocket.Core.LogLevel> RulesLogLevelAsync(long rulesId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task SetRulesLogLevelAsync(long rulesId, TouchSocket.Core.LogLevel logLevel); | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<string> RulesLogPathAsync(long rulesId); | ||||
|     [DmtpRpc] | ||||
|     Task<Rules> GetRuleRuntimesAsync(long rulesId); | ||||
|     [DmtpRpc] | ||||
|     Task DeleteRuleRuntimesAsync(List<long> ids); | ||||
|     [DmtpRpc] | ||||
|     Task EditRuleRuntimesAsync(Rules rules); | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 清除所有规则 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     Task ClearRulesAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 删除规则 | ||||
|     /// </summary> | ||||
|     /// <param name="ids">待删除规则的ID列表</param> | ||||
|     [DmtpRpc] | ||||
|     Task<bool> DeleteRulesAsync(List<long> ids); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 从缓存/数据库获取全部信息 | ||||
|     /// </summary> | ||||
|     /// <returns>规则列表</returns> | ||||
|     [DmtpRpc] | ||||
|     Task<List<Rules>> GetAllAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 报表查询 | ||||
| @@ -203,168 +401,84 @@ public interface IManagementRpcServer : IRpcServer | ||||
|     /// <param name="option">查询条件</param> | ||||
|     /// <param name="filterKeyValueAction">查询条件</param> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<QueryData<Rules>> RulesPageAsync(QueryPageOptions option, FilterKeyValueAction filterKeyValueAction = null); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 添加插件 | ||||
|     /// </summary> | ||||
|     /// <param name="plugin"></param> | ||||
|     /// <returns></returns> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task SavePluginByPathAsync(PluginAddPathInput plugin); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 保存规则 | ||||
|     /// </summary> | ||||
|     /// <param name="input">规则对象</param> | ||||
|     /// <param name="type">保存类型</param> | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> SaveRulesAsync(Rules input, ItemChangedType type); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> GetPluginNameAsync(long channelId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task RestartChannelAsync(long channelId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|     [DmtpRpc] | ||||
|     Task CopyChannelAsync(int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long channelId, bool AutoRestartThread); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<ChannelRuntime>> OnChannelQueryAsync(QueryPageOptions options); | ||||
|     [DmtpRpc] | ||||
|     Task<List<Channel>> GetChannelListAsync(QueryPageOptions options, int max = 0); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> BatchEditChannelAsync(List<Channel> models, Channel oldModel, Channel model, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> DeleteChannelAsync(List<long> ids, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> ClearChannelAsync(bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync(USheetDatas input, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelFileAsync(string filePath, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<USheetDatas> ExportChannelAsync(List<Channel> channels); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> GetChannelNameAsync(long channelId); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task SetDeviceLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread); | ||||
|     [DmtpRpc] | ||||
|     Task<TouchSocket.Core.LogLevel> DeviceLogLevelAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> BatchEditDeviceAsync(List<Device> models, Device oldModel, Device model, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> DeleteDeviceAsync(List<long> ids, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync(USheetDatas input, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<USheetDatas> ExportDeviceAsync(List<Device> devices); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> ExportDeviceFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync(VirtualizeQueryOption option, long deviceId, long channelId); | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceFileAsync(string filePath, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task DeviceRedundantThreadAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task RestartDeviceAsync(long id, bool deleteCache); | ||||
|     [DmtpRpc] | ||||
|     Task PauseThreadAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync(QueryPageOptions options); | ||||
|     [DmtpRpc] | ||||
|     Task<List<Device>> GetDeviceListAsync(QueryPageOptions option, int v); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> ClearDeviceAsync(bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> IsRedundantDeviceAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task<string> GetDeviceNameAsync(long redundantDeviceId); | ||||
|  | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<List<SelectedItem>> GetDeviceItemsAsync(bool isCollect); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> GetDevicePluginNameAsync(long id); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> BatchEditVariableAsync(List<Variable> models, Variable oldModel, Variable model, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> DeleteVariableAsync(List<long> ids, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> ClearVariableAsync(bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> BatchSaveVariableAsync(List<Variable> input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> SaveVariableAsync(Variable input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task CopyVariableAsync(List<Variable> Model, int CopyCount, string CopyVariableNamePrefix, int CopyVariableNameSuffixNumber, bool AutoRestartThread); | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<VariableRuntime>> OnVariableQueryAsync(QueryPageOptions options); | ||||
|     [DmtpRpc] | ||||
|     Task<string> ExportVariableFileAsync(GatewayExportFilter exportFilter); | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task SetDeviceLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<List<Variable>> GetVariableListAsync(QueryPageOptions option, int v); | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task SetRedundancyLogLevelAsync(TouchSocket.Core.LogLevel logLevel); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<USheetDatas> ExportVariableAsync(List<Variable> models, string? sortName, SortOrder sortOrder); | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task SetRulesLogLevelAsync(long rulesId, TouchSocket.Core.LogLevel logLevel); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableUSheetDatasAsync(USheetDatas data, bool restart); | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> StartBusinessChannelEnableAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<OperResult<object>> OnWriteVariableAsync(long id, string writeData); | ||||
|  | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<bool> StartCollectChannelEnableAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableFileAsync(string filePath, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync(); | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task StartRedundancyTaskAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task StopRedundancyTaskAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task<AuthorizeInfo> TryAuthorizeAsync(string password); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<AuthorizeInfo> TryGetAuthorizeInfoAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task UnAuthorizeAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<string> UUIDAsync(); | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://thingsgateway.cn/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using TouchSocket.Rpc; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public interface IPluginRpcServer : IRpcServer | ||||
| { | ||||
|  | ||||
| } | ||||
| @@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Components.Forms; | ||||
| using ThingsGateway.Authentication; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| using TouchSocket.Dmtp.Rpc; | ||||
| using TouchSocket.Rpc; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| @@ -24,188 +23,78 @@ namespace ThingsGateway.Gateway.Application; | ||||
| public partial class ManagementRpcServer : IRpcServer, IManagementRpcServer, IBackendLogService, IRpcLogService, IRestartService, IAuthenticationService, IChannelEnableService, IRedundancyHostedService, IRedundancyService, ITextFileReadService, IPluginPageService, IRealAlarmService, IChannelPageService, IDevicePageService, IVariablePageService | ||||
| { | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     public Task DeleteBackendLogAsync() => App.GetService<IBackendLogService>().DeleteBackendLogAsync(); | ||||
|     [DmtpRpc] | ||||
|     public Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day) => App.GetService<IBackendLogService>().BackendLogStatisticsByDayAsync(day); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     public Task<List<BackendLog>> GetNewBackendLogAsync() => App.GetService<IBackendLogService>().GetNewBackendLogAsync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     public Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option) => App.GetService<IBackendLogService>().BackendLogPageAsync(option); | ||||
|  | ||||
|     public Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day) => App.GetService<IBackendLogService>().BackendLogStatisticsByDayAsync(day); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     public async Task<Dictionary<string, Dictionary<string, OperResult<object>>>> RpcAsync(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas) | ||||
|     { | ||||
|         var data = await GlobalData.RpcService.InvokeDeviceMethodAsync($"Management[{(callContext.Caller is ITcpSession tcpSession ? tcpSession.GetIPPort() : string.Empty)}]", deviceDatas, callContext.Token).ConfigureAwait(false); | ||||
|     public Task<bool> BatchEditChannelAsync(List<Channel> models, Channel oldModel, Channel model, bool restart) => | ||||
|         App.GetService<IChannelPageService>().BatchEditChannelAsync(models, oldModel, model, restart); | ||||
|  | ||||
|         return data.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => b.Value.GetOperResult())); | ||||
|     } | ||||
|     public Task<bool> BatchEditDeviceAsync(List<Device> models, Device oldModel, Device model, bool restart) => | ||||
|         App.GetService<IDevicePageService>().BatchEditDeviceAsync(models, oldModel, model, restart); | ||||
|  | ||||
|     public Task DeleteRpcLogAsync() => App.GetService<IRpcLogService>().DeleteRpcLogAsync(); | ||||
|     public Task<bool> BatchEditVariableAsync(List<Variable> models, Variable oldModel, Variable model, bool restart) => | ||||
|         App.GetService<IVariablePageService>().BatchEditVariableAsync(models, oldModel, model, restart); | ||||
|  | ||||
|     public Task<List<RpcLog>> GetNewRpcLogAsync() => App.GetService<IRpcLogService>().GetNewRpcLogAsync(); | ||||
|  | ||||
|     public Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option) => App.GetService<IRpcLogService>().RpcLogPageAsync(option); | ||||
|  | ||||
|     public Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day) => App.GetService<IRpcLogService>().RpcLogStatisticsByDayAsync(day); | ||||
|  | ||||
|     public Task RestartServerAsync() => App.GetService<IRestartService>().RestartServerAsync(); | ||||
|  | ||||
|     public Task<string> UUIDAsync() => App.GetService<IAuthenticationService>().UUIDAsync(); | ||||
|  | ||||
|     public Task<AuthorizeInfo> TryAuthorizeAsync(string password) => App.GetService<IAuthenticationService>().TryAuthorizeAsync(password); | ||||
|  | ||||
|     public Task<AuthorizeInfo> TryGetAuthorizeInfoAsync() => App.GetService<IAuthenticationService>().TryGetAuthorizeInfoAsync(); | ||||
|  | ||||
|     public Task UnAuthorizeAsync() => App.GetService<IAuthenticationService>().UnAuthorizeAsync(); | ||||
|  | ||||
|     public Task<bool> StartBusinessChannelEnableAsync() => App.GetService<IChannelEnableService>().StartBusinessChannelEnableAsync(); | ||||
|  | ||||
|  | ||||
|     public Task<bool> StartCollectChannelEnableAsync() => App.GetService<IChannelEnableService>().StartCollectChannelEnableAsync(); | ||||
|  | ||||
|     public Task StartRedundancyTaskAsync() => App.GetService<IRedundancyHostedService>().StartRedundancyTaskAsync(); | ||||
|  | ||||
|     public Task StopRedundancyTaskAsync() => App.GetService<IRedundancyHostedService>().StopRedundancyTaskAsync(); | ||||
|  | ||||
|     public Task RedundancyForcedSync() => App.GetService<IRedundancyHostedService>().RedundancyForcedSync(); | ||||
|  | ||||
|  | ||||
|     public Task EditRedundancyOptionAsync(RedundancyOptions input) => App.GetService<IRedundancyService>().EditRedundancyOptionAsync(input); | ||||
|  | ||||
|     public Task<RedundancyOptions> GetRedundancyAsync() => App.GetService<IRedundancyService>().GetRedundancyAsync(); | ||||
|  | ||||
|     public Task<LogLevel> RedundancyLogLevelAsync() => App.GetService<IRedundancyHostedService>().RedundancyLogLevelAsync(); | ||||
|  | ||||
|  | ||||
|     public Task SetRedundancyLogLevelAsync(LogLevel logLevel) => App.GetService<IRedundancyHostedService>().SetRedundancyLogLevelAsync(logLevel); | ||||
|  | ||||
|     public Task<string> RedundancyLogPathAsync() => App.GetService<IRedundancyHostedService>().RedundancyLogPathAsync(); | ||||
|  | ||||
|     public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath) => App.GetService<ITextFileReadService>().GetLogFilesAsync(directoryPath); | ||||
|  | ||||
|     public Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200) => App.GetService<ITextFileReadService>().LastLogDataAsync(file, lineCount); | ||||
|  | ||||
|     public Task<List<PluginInfo>> GetPluginsAsync(PluginTypeEnum? pluginType = null) => App.GetService<IPluginPageService>().GetPluginsAsync(pluginType); | ||||
|  | ||||
|     public Task<QueryData<PluginInfo>> PluginPageAsync(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null) => App.GetService<IPluginPageService>().PluginPageAsync(options, pluginTypeEnum); | ||||
|  | ||||
|     public Task ReloadPluginAsync() => App.GetService<IPluginPageService>().ReloadPluginAsync(); | ||||
|  | ||||
|  | ||||
|  | ||||
|     public Task SavePluginByPathAsync(PluginAddPathInput plugin) => App.GetService<IPluginPageService>().SavePluginByPathAsync(plugin); | ||||
|  | ||||
|     public Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync() => App.GetService<IRealAlarmService>().GetCurrentUserRealAlarmVariablesAsync(); | ||||
|  | ||||
|     public Task<IEnumerable<SelectedItem>> GetCurrentUserDeviceSelectedItemsAsync(string searchText, int startIndex, int count) => App.GetService<IGlobalDataService>().GetCurrentUserDeviceSelectedItemsAsync(searchText, startIndex, count); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> GetCurrentUserDeviceVariableSelectedItemsAsync(string deviceText, string searchText, int startIndex, int count) => App.GetService<IGlobalDataService>().GetCurrentUserDeviceVariableSelectedItemsAsync(deviceText, searchText, startIndex, count); | ||||
|  | ||||
|     public Task<TouchSocket.Core.LogLevel> RulesLogLevelAsync(long rulesId) => App.GetService<IRulesEngineHostedService>().RulesLogLevelAsync(rulesId); | ||||
|     public Task SetRulesLogLevelAsync(long rulesId, TouchSocket.Core.LogLevel logLevel) => App.GetService<IRulesEngineHostedService>().SetRulesLogLevelAsync(rulesId, logLevel); | ||||
|     public Task<string> RulesLogPathAsync(long rulesId) => App.GetService<IRulesEngineHostedService>().RulesLogPathAsync(rulesId); | ||||
|     public Task<Rules> GetRuleRuntimesAsync(long rulesId) => App.GetService<IRulesEngineHostedService>().GetRuleRuntimesAsync(rulesId); | ||||
|  | ||||
|  | ||||
|     public Task DeleteRuleRuntimesAsync(List<long> ids) => App.GetService<IRulesEngineHostedService>().DeleteRuleRuntimesAsync(ids); | ||||
|  | ||||
|     public Task EditRuleRuntimesAsync(Rules rules) => App.GetService<IRulesEngineHostedService>().EditRuleRuntimesAsync(rules); | ||||
|  | ||||
|     public Task ClearRulesAsync() => App.GetService<IRulesService>().ClearRulesAsync(); | ||||
|  | ||||
|  | ||||
|     public Task<bool> DeleteRulesAsync(List<long> ids) => App.GetService<IRulesService>().DeleteRulesAsync(ids); | ||||
|  | ||||
|     public Task<List<Rules>> GetAllAsync() => App.GetService<IRulesService>().GetAllAsync(); | ||||
|  | ||||
|  | ||||
|     public Task<QueryData<Rules>> RulesPageAsync(QueryPageOptions option, FilterKeyValueAction filterKeyValueAction = null) => App.GetService<IRulesService>().RulesPageAsync(option, filterKeyValueAction); | ||||
|  | ||||
|  | ||||
|     public Task<bool> SaveRulesAsync(Rules input, ItemChangedType type) => App.GetService<IRulesService>().SaveRulesAsync(input, type); | ||||
|  | ||||
|     public Task<string> GetPluginNameAsync(long channelId) => App.GetService<IChannelPageService>().GetPluginNameAsync(channelId); | ||||
|  | ||||
|     public Task RestartChannelAsync(long channelId) => | ||||
|     App.GetService<IChannelPageService>().RestartChannelAsync(channelId); | ||||
|     public Task<bool> BatchSaveVariableAsync(List<Variable> input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IVariablePageService>().BatchSaveVariableAsync(input, type, restart); | ||||
|  | ||||
|     public Task<LogLevel> ChannelLogLevelAsync(long id) => | ||||
|         App.GetService<IChannelPageService>().ChannelLogLevelAsync(id); | ||||
|  | ||||
|     public Task SetChannelLogLevelAsync(long id, LogLevel logLevel) => | ||||
|         App.GetService<IChannelPageService>().SetChannelLogLevelAsync(id, logLevel); | ||||
|     public Task<bool> ClearChannelAsync(bool restart) => | ||||
|         App.GetService<IChannelPageService>().ClearChannelAsync(restart); | ||||
|  | ||||
|     public Task<bool> ClearDeviceAsync(bool restart) => | ||||
|         App.GetService<IDevicePageService>().ClearDeviceAsync(restart); | ||||
|  | ||||
|     public Task ClearRulesAsync() => App.GetService<IRulesService>().ClearRulesAsync(); | ||||
|  | ||||
|     public Task<bool> ClearVariableAsync(bool restart) => | ||||
|         App.GetService<IVariablePageService>().ClearVariableAsync(restart); | ||||
|  | ||||
|     public Task CopyChannelAsync(int copyCount, string copyChannelNamePrefix, int copyChannelNameSuffixNumber, | ||||
|         string copyDeviceNamePrefix, int copyDeviceNameSuffixNumber, long channelId, bool restart) => | ||||
|         App.GetService<IChannelPageService>().CopyChannelAsync(copyCount, copyChannelNamePrefix, copyChannelNameSuffixNumber, | ||||
|             copyDeviceNamePrefix, copyDeviceNameSuffixNumber, channelId, restart); | ||||
|  | ||||
|     public Task<QueryData<ChannelRuntime>> OnChannelQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IChannelPageService>().OnChannelQueryAsync(options); | ||||
|  | ||||
|     public Task<List<Channel>> GetChannelListAsync(QueryPageOptions options, int max = 0) => | ||||
|         App.GetService<IChannelPageService>().GetChannelListAsync(options, max); | ||||
|  | ||||
|     public Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IChannelPageService>().SaveChannelAsync(input, type, restart); | ||||
|  | ||||
|     public Task<bool> BatchEditChannelAsync(List<Channel> models, Channel oldModel, Channel model, bool restart) => | ||||
|         App.GetService<IChannelPageService>().BatchEditChannelAsync(models, oldModel, model, restart); | ||||
|  | ||||
|     public Task<bool> DeleteChannelAsync(List<long> ids, bool restart) => | ||||
|         App.GetService<IChannelPageService>().DeleteChannelAsync(ids, restart); | ||||
|  | ||||
|     public Task<bool> ClearChannelAsync(bool restart) => | ||||
|         App.GetService<IChannelPageService>().ClearChannelAsync(restart); | ||||
|  | ||||
|     public Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelAsync(upData, insertData, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync(USheetDatas input, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelUSheetDatasAsync(input, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelFileAsync(string filePath, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelFileAsync(filePath, restart); | ||||
|  | ||||
|     public Task<USheetDatas> ExportChannelAsync(List<Channel> channels) => | ||||
|         App.GetService<IChannelPageService>().ExportChannelAsync(channels); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option) => | ||||
|         App.GetService<IChannelPageService>().OnChannelSelectedItemQueryAsync(option); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IBrowserFile file, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelAsync(file, restart); | ||||
|  | ||||
|     public Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter) => | ||||
|         App.GetService<IChannelPageService>().ExportChannelFileAsync(exportFilter); | ||||
|  | ||||
|     public Task<string> GetChannelNameAsync(long id) => | ||||
|         App.GetService<IChannelPageService>().GetChannelNameAsync(id); | ||||
|  | ||||
|     public Task SetDeviceLogLevelAsync(long id, LogLevel logLevel) => | ||||
|         App.GetService<IDevicePageService>().SetDeviceLogLevelAsync(id, logLevel); | ||||
|  | ||||
|     public Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread) => | ||||
|     App.GetService<IDevicePageService>().CopyDeviceAsync(CopyCount, CopyDeviceNamePrefix, CopyDeviceNameSuffixNumber, deviceId, AutoRestartThread); | ||||
|  | ||||
|     public Task<LogLevel> DeviceLogLevelAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().DeviceLogLevelAsync(id); | ||||
|     public Task CopyVariableAsync(List<Variable> model, int copyCount, string copyVariableNamePrefix, int copyVariableNameSuffixNumber, bool restart) => | ||||
|         App.GetService<IVariablePageService>().CopyVariableAsync(model, copyCount, copyVariableNamePrefix, copyVariableNameSuffixNumber, restart); | ||||
|  | ||||
|     public Task<bool> BatchEditDeviceAsync(List<Device> models, Device oldModel, Device model, bool restart) => | ||||
|         App.GetService<IDevicePageService>().BatchEditDeviceAsync(models, oldModel, model, restart); | ||||
|  | ||||
|     public Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IDevicePageService>().SaveDeviceAsync(input, type, restart); | ||||
|     public Task DeleteBackendLogAsync() => App.GetService<IBackendLogService>().DeleteBackendLogAsync(); | ||||
|     public Task<bool> DeleteChannelAsync(List<long> ids, bool restart) => | ||||
|         App.GetService<IChannelPageService>().DeleteChannelAsync(ids, restart); | ||||
|  | ||||
|     public Task<bool> DeleteDeviceAsync(List<long> ids, bool restart) => | ||||
|         App.GetService<IDevicePageService>().DeleteDeviceAsync(ids, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync(USheetDatas input, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceUSheetDatasAsync(input, restart); | ||||
|     public Task DeleteRpcLogAsync() => App.GetService<IRpcLogService>().DeleteRpcLogAsync(); | ||||
|  | ||||
|     public Task DeleteRuleRuntimesAsync(List<long> ids) => App.GetService<IRulesEngineHostedService>().DeleteRuleRuntimesAsync(ids); | ||||
|  | ||||
|     public Task<bool> DeleteRulesAsync(List<long> ids) => App.GetService<IRulesService>().DeleteRulesAsync(ids); | ||||
|  | ||||
|     public Task<bool> DeleteVariableAsync(List<long> ids, bool restart) => | ||||
|     App.GetService<IVariablePageService>().DeleteVariableAsync(ids, restart); | ||||
|  | ||||
|     public Task<LogLevel> DeviceLogLevelAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().DeviceLogLevelAsync(id); | ||||
|  | ||||
|     public Task DeviceRedundantThreadAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().DeviceRedundantThreadAsync(id); | ||||
|  | ||||
|     public Task EditRedundancyOptionAsync(RedundancyOptions input) => App.GetService<IRedundancyService>().EditRedundancyOptionAsync(input); | ||||
|  | ||||
|     public Task EditRuleRuntimesAsync(Rules rules) => App.GetService<IRulesEngineHostedService>().EditRuleRuntimesAsync(rules); | ||||
|  | ||||
|     public Task<USheetDatas> ExportChannelAsync(List<Channel> channels) => | ||||
|         App.GetService<IChannelPageService>().ExportChannelAsync(channels); | ||||
|  | ||||
|     public Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter) => | ||||
|         App.GetService<IChannelPageService>().ExportChannelFileAsync(exportFilter); | ||||
|  | ||||
|     public Task<USheetDatas> ExportDeviceAsync(List<Device> devices) => | ||||
|         App.GetService<IDevicePageService>().ExportDeviceAsync(devices); | ||||
| @@ -213,80 +102,72 @@ public partial class ManagementRpcServer : IRpcServer, IManagementRpcServer, IBa | ||||
|     public Task<string> ExportDeviceFileAsync(GatewayExportFilter exportFilter) => | ||||
|         App.GetService<IDevicePageService>().ExportDeviceFileAsync(exportFilter); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync(VirtualizeQueryOption option, long deviceId, long channelId) => | ||||
|         App.GetService<IDevicePageService>().OnRedundantDevicesQueryAsync(option, deviceId, channelId); | ||||
|     public Task<USheetDatas> ExportVariableAsync(List<Variable> models, string? sortName, SortOrder sortOrder) => | ||||
|         App.GetService<IVariablePageService>().ExportVariableAsync(models, sortName, sortOrder); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceFileAsync(string filePath, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceFileAsync(filePath, restart); | ||||
|     public Task<string> ExportVariableFileAsync(GatewayExportFilter exportFilter) => App.GetService<IVariablePageService>().ExportVariableFileAsync(exportFilter); | ||||
|  | ||||
|     public Task DeviceRedundantThreadAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().DeviceRedundantThreadAsync(id); | ||||
|     public Task<List<Rules>> GetAllRulesAsync() => App.GetService<IRulesService>().GetAllRulesAsync(); | ||||
|  | ||||
|     public Task RestartDeviceAsync(long id, bool deleteCache) => | ||||
|         App.GetService<IDevicePageService>().RestartDeviceAsync(id, deleteCache); | ||||
|     public Task<List<Channel>> GetChannelListAsync(QueryPageOptions options, int max = 0) => | ||||
|         App.GetService<IChannelPageService>().GetChannelListAsync(options, max); | ||||
|  | ||||
|     public Task PauseThreadAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().PauseThreadAsync(id); | ||||
|     public Task<string> GetChannelNameAsync(long id) => | ||||
|         App.GetService<IChannelPageService>().GetChannelNameAsync(id); | ||||
|  | ||||
|     public Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IDevicePageService>().OnDeviceQueryAsync(options); | ||||
|     public Task<IEnumerable<SelectedItem>> GetCurrentUserDeviceSelectedItemsAsync(string searchText, int startIndex, int count) => App.GetService<IGlobalDataService>().GetCurrentUserDeviceSelectedItemsAsync(searchText, startIndex, count); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> GetCurrentUserDeviceVariableSelectedItemsAsync(string deviceText, string searchText, int startIndex, int count) => App.GetService<IGlobalDataService>().GetCurrentUserDeviceVariableSelectedItemsAsync(deviceText, searchText, startIndex, count); | ||||
|  | ||||
|     public Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync() => App.GetService<IRealAlarmService>().GetCurrentUserRealAlarmVariablesAsync(); | ||||
|  | ||||
|     public Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync() => App.GetService<IDevicePageService>().GetDeviceIdNamesAsync(); | ||||
|  | ||||
|     public Task<List<Device>> GetDeviceListAsync(QueryPageOptions option, int v) => | ||||
|         App.GetService<IDevicePageService>().GetDeviceListAsync(option, v); | ||||
|  | ||||
|     public Task<bool> ClearDeviceAsync(bool restart) => | ||||
|         App.GetService<IDevicePageService>().ClearDeviceAsync(restart); | ||||
|  | ||||
|     public Task<bool> IsRedundantDeviceAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().IsRedundantDeviceAsync(id); | ||||
|  | ||||
|     public Task<string> GetDeviceNameAsync(long redundantDeviceId) => | ||||
|         App.GetService<IDevicePageService>().GetDeviceNameAsync(redundantDeviceId); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceAsync(IBrowserFile file, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceAsync(file, restart); | ||||
|  | ||||
|     public Task<List<SelectedItem>> GetDeviceItemsAsync(bool isCollect) => | ||||
|         App.GetService<IDevicePageService>().GetDeviceItemsAsync(isCollect); | ||||
|  | ||||
|     public Task<string> GetDevicePluginNameAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().GetDevicePluginNameAsync(id); | ||||
|  | ||||
|     public Task<bool> BatchEditVariableAsync(List<Variable> models, Variable oldModel, Variable model, bool restart) => | ||||
|         App.GetService<IVariablePageService>().BatchEditVariableAsync(models, oldModel, model, restart); | ||||
|     public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath) => App.GetService<ITextFileReadService>().GetLogFilesAsync(directoryPath); | ||||
|  | ||||
|     public Task<bool> DeleteVariableAsync(List<long> ids, bool restart) => | ||||
|     App.GetService<IVariablePageService>().DeleteVariableAsync(ids, restart); | ||||
|     public Task<List<BackendLog>> GetNewBackendLogAsync() => App.GetService<IBackendLogService>().GetNewBackendLogAsync(); | ||||
|     public Task<List<RpcLog>> GetNewRpcLogAsync() => App.GetService<IRpcLogService>().GetNewRpcLogAsync(); | ||||
|  | ||||
|     public Task<bool> ClearVariableAsync(bool restart) => | ||||
|         App.GetService<IVariablePageService>().ClearVariableAsync(restart); | ||||
|     public Task<string> GetPluginNameAsync(long channelId) => App.GetService<IChannelPageService>().GetPluginNameAsync(channelId); | ||||
|  | ||||
|     public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart) => | ||||
|         App.GetService<IVariablePageService>().InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart); | ||||
|     public Task<List<PluginInfo>> GetPluginsAsync(PluginTypeEnum? pluginType = null) => App.GetService<IPluginPageService>().GetPluginsAsync(pluginType); | ||||
|  | ||||
|     public Task<bool> BatchSaveVariableAsync(List<Variable> input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IVariablePageService>().BatchSaveVariableAsync(input, type, restart); | ||||
|     public Task<RedundancyOptions> GetRedundancyAsync() => App.GetService<IRedundancyService>().GetRedundancyAsync(); | ||||
|  | ||||
|     public Task<bool> SaveVariableAsync(Variable input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IVariablePageService>().SaveVariableAsync(input, type, restart); | ||||
|  | ||||
|     public Task CopyVariableAsync(List<Variable> model, int copyCount, string copyVariableNamePrefix, int copyVariableNameSuffixNumber, bool restart) => | ||||
|         App.GetService<IVariablePageService>().CopyVariableAsync(model, copyCount, copyVariableNamePrefix, copyVariableNameSuffixNumber, restart); | ||||
|  | ||||
|     public Task<QueryData<VariableRuntime>> OnVariableQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IVariablePageService>().OnVariableQueryAsync(options); | ||||
|     public Task<Rules> GetRuleRuntimesAsync(long rulesId) => App.GetService<IRulesEngineHostedService>().GetRuleRuntimesAsync(rulesId); | ||||
|  | ||||
|     public Task<List<Variable>> GetVariableListAsync(QueryPageOptions option, int v) => | ||||
|         App.GetService<IVariablePageService>().GetVariableListAsync(option, v); | ||||
|  | ||||
|     public Task<USheetDatas> ExportVariableAsync(List<Variable> models, string? sortName, SortOrder sortOrder) => | ||||
|         App.GetService<IVariablePageService>().ExportVariableAsync(models, sortName, sortOrder); | ||||
|     public Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelAsync(upData, insertData, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableUSheetDatasAsync(USheetDatas data, bool restart) => | ||||
|         App.GetService<IVariablePageService>().ImportVariableUSheetDatasAsync(data, restart); | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IBrowserFile file, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelAsync(file, restart); | ||||
|  | ||||
|     public Task<OperResult<object>> OnWriteVariableAsync(long id, string writeData) => | ||||
|         App.GetService<IVariablePageService>().OnWriteVariableAsync(id, writeData); | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelFileAsync(string filePath, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelFileAsync(filePath, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync(USheetDatas input, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelUSheetDatasAsync(input, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceAsync(IBrowserFile file, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceAsync(file, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceFileAsync(string filePath, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceFileAsync(filePath, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync(USheetDatas input, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceUSheetDatasAsync(input, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableAsync(IBrowserFile a, bool restart) => | ||||
|         App.GetService<IVariablePageService>().ImportVariableAsync(a, restart); | ||||
| @@ -294,7 +175,113 @@ public partial class ManagementRpcServer : IRpcServer, IManagementRpcServer, IBa | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableFileAsync(string filePath, bool restart) => | ||||
|         App.GetService<IVariablePageService>().ImportVariableFileAsync(filePath, restart); | ||||
|  | ||||
|     public Task<string> ExportVariableFileAsync(GatewayExportFilter exportFilter) => App.GetService<IVariablePageService>().ExportVariableFileAsync(exportFilter); | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableUSheetDatasAsync(USheetDatas data, bool restart) => | ||||
|         App.GetService<IVariablePageService>().ImportVariableUSheetDatasAsync(data, restart); | ||||
|  | ||||
|     public Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync() => App.GetService<IDevicePageService>().GetDeviceIdNamesAsync(); | ||||
|     public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart) => | ||||
|         App.GetService<IVariablePageService>().InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart); | ||||
|  | ||||
|     public Task<bool> IsRedundantDeviceAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().IsRedundantDeviceAsync(id); | ||||
|  | ||||
|     public Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200) => App.GetService<ITextFileReadService>().LastLogDataAsync(file, lineCount); | ||||
|  | ||||
|     public Task<QueryData<ChannelRuntime>> OnChannelQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IChannelPageService>().OnChannelQueryAsync(options); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option) => | ||||
|         App.GetService<IChannelPageService>().OnChannelSelectedItemQueryAsync(option); | ||||
|  | ||||
|     public Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IDevicePageService>().OnDeviceQueryAsync(options); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> OnDeviceSelectedItemQueryAsync(VirtualizeQueryOption option, bool isCollect) => | ||||
|         App.GetService<IDevicePageService>().OnDeviceSelectedItemQueryAsync(option, isCollect); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync(VirtualizeQueryOption option, long deviceId, long channelId) => | ||||
|         App.GetService<IDevicePageService>().OnRedundantDevicesQueryAsync(option, deviceId, channelId); | ||||
|  | ||||
|     public Task<QueryData<VariableRuntime>> OnVariableQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IVariablePageService>().OnVariableQueryAsync(options); | ||||
|  | ||||
|     public Task<OperResult<object>> OnWriteVariableAsync(long id, string writeData) => | ||||
|         App.GetService<IVariablePageService>().OnWriteVariableAsync(id, writeData); | ||||
|  | ||||
|     public Task PauseThreadAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().PauseThreadAsync(id); | ||||
|  | ||||
|     public Task<QueryData<PluginInfo>> PluginPageAsync(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null) => App.GetService<IPluginPageService>().PluginPageAsync(options, pluginTypeEnum); | ||||
|  | ||||
|     public Task RedundancyForcedSync() => App.GetService<IRedundancyHostedService>().RedundancyForcedSync(); | ||||
|  | ||||
|     public Task<LogLevel> RedundancyLogLevelAsync() => App.GetService<IRedundancyHostedService>().RedundancyLogLevelAsync(); | ||||
|  | ||||
|     public Task<string> RedundancyLogPathAsync() => App.GetService<IRedundancyHostedService>().RedundancyLogPathAsync(); | ||||
|  | ||||
|     public Task ReloadPluginAsync() => App.GetService<IPluginPageService>().ReloadPluginAsync(); | ||||
|  | ||||
|     public Task RestartChannelAsync(long channelId) => | ||||
|     App.GetService<IChannelPageService>().RestartChannelAsync(channelId); | ||||
|  | ||||
|     public Task RestartChannelsAsync() => | ||||
|     App.GetService<IChannelPageService>().RestartChannelsAsync(); | ||||
|  | ||||
|     public Task RestartDeviceAsync(long id, bool deleteCache) => | ||||
|         App.GetService<IDevicePageService>().RestartDeviceAsync(id, deleteCache); | ||||
|  | ||||
|     public Task RestartServerAsync() => App.GetService<IRestartService>().RestartServerAsync(); | ||||
|  | ||||
|     public async Task<Dictionary<string, Dictionary<string, OperResult<object>>>> RpcAsync(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas) | ||||
|     { | ||||
|         var data = await GlobalData.RpcService.InvokeDeviceMethodAsync($"Management[{(callContext.Caller is ITcpSession tcpSession ? tcpSession.GetIPPort() : string.Empty)}]", deviceDatas, callContext.Token).ConfigureAwait(false); | ||||
|  | ||||
|         return data.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => b.Value.GetOperResult())); | ||||
|     } | ||||
|     public Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option) => App.GetService<IRpcLogService>().RpcLogPageAsync(option); | ||||
|  | ||||
|     public Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day) => App.GetService<IRpcLogService>().RpcLogStatisticsByDayAsync(day); | ||||
|     public Task<TouchSocket.Core.LogLevel> RulesLogLevelAsync(long rulesId) => App.GetService<IRulesEngineHostedService>().RulesLogLevelAsync(rulesId); | ||||
|  | ||||
|     public Task<string> RulesLogPathAsync(long rulesId) => App.GetService<IRulesEngineHostedService>().RulesLogPathAsync(rulesId); | ||||
|  | ||||
|     public Task<QueryData<Rules>> RulesPageAsync(QueryPageOptions option, FilterKeyValueAction filterKeyValueAction = null) => App.GetService<IRulesService>().RulesPageAsync(option, filterKeyValueAction); | ||||
|  | ||||
|     public Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IChannelPageService>().SaveChannelAsync(input, type, restart); | ||||
|  | ||||
|     public Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IDevicePageService>().SaveDeviceAsync(input, type, restart); | ||||
|  | ||||
|     public Task SavePluginByPathAsync(PluginAddPathInput plugin) => App.GetService<IPluginPageService>().SavePluginByPathAsync(plugin); | ||||
|  | ||||
|     public Task<bool> SaveRulesAsync(Rules input, ItemChangedType type) => App.GetService<IRulesService>().SaveRulesAsync(input, type); | ||||
|  | ||||
|     public Task<bool> SaveVariableAsync(Variable input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IVariablePageService>().SaveVariableAsync(input, type, restart); | ||||
|  | ||||
|     public Task SetChannelLogLevelAsync(long id, LogLevel logLevel) => | ||||
|         App.GetService<IChannelPageService>().SetChannelLogLevelAsync(id, logLevel); | ||||
|  | ||||
|     public Task SetDeviceLogLevelAsync(long id, LogLevel logLevel) => | ||||
|         App.GetService<IDevicePageService>().SetDeviceLogLevelAsync(id, logLevel); | ||||
|  | ||||
|     public Task SetRedundancyLogLevelAsync(LogLevel logLevel) => App.GetService<IRedundancyHostedService>().SetRedundancyLogLevelAsync(logLevel); | ||||
|  | ||||
|     public Task SetRulesLogLevelAsync(long rulesId, TouchSocket.Core.LogLevel logLevel) => App.GetService<IRulesEngineHostedService>().SetRulesLogLevelAsync(rulesId, logLevel); | ||||
|  | ||||
|     public Task<bool> StartBusinessChannelEnableAsync() => App.GetService<IChannelEnableService>().StartBusinessChannelEnableAsync(); | ||||
|  | ||||
|     public Task<bool> StartCollectChannelEnableAsync() => App.GetService<IChannelEnableService>().StartCollectChannelEnableAsync(); | ||||
|  | ||||
|     public Task StartRedundancyTaskAsync() => App.GetService<IRedundancyHostedService>().StartRedundancyTaskAsync(); | ||||
|  | ||||
|     public Task StopRedundancyTaskAsync() => App.GetService<IRedundancyHostedService>().StopRedundancyTaskAsync(); | ||||
|  | ||||
|     public Task<AuthorizeInfo> TryAuthorizeAsync(string password) => App.GetService<IAuthenticationService>().TryAuthorizeAsync(password); | ||||
|  | ||||
|     public Task<AuthorizeInfo> TryGetAuthorizeInfoAsync() => App.GetService<IAuthenticationService>().TryGetAuthorizeInfoAsync(); | ||||
|  | ||||
|     public Task UnAuthorizeAsync() => App.GetService<IAuthenticationService>().UnAuthorizeAsync(); | ||||
|  | ||||
|     public Task<string> UUIDAsync() => App.GetService<IAuthenticationService>().UUIDAsync(); | ||||
| } | ||||
|   | ||||
| @@ -103,6 +103,10 @@ public partial class ManagementTask : AsyncDisposableObject | ||||
|                        store.RegisterServer<IManagementRpcServer>(new ManagementRpcServer()); | ||||
|                        store.RegisterServer<IUpgradeRpcServer>(new UpgradeRpcServer()); | ||||
|  | ||||
|                        foreach (var type in App.EffectiveTypes.Where(p => typeof(IPluginRpcServer).IsAssignableFrom(p) && !p.IsAbstract && p.IsClass)) | ||||
|                        { | ||||
|                            store.RegisterServer(type); | ||||
|                        } | ||||
|                    }); | ||||
|  | ||||
|                }) | ||||
| @@ -162,7 +166,10 @@ public partial class ManagementTask : AsyncDisposableObject | ||||
|                    { | ||||
|                        store.RegisterServer<IManagementRpcServer>(new ManagementRpcServer()); | ||||
|                        store.RegisterServer<IUpgradeRpcServer>(new UpgradeRpcServer()); | ||||
|  | ||||
|                        foreach (var type in App.EffectiveTypes.Where(p => typeof(IPluginRpcServer).IsAssignableFrom(p) && !p.IsAbstract && p.IsClass)) | ||||
|                        { | ||||
|                            store.RegisterServer(type); | ||||
|                        } | ||||
|                    }); | ||||
|  | ||||
|                }) | ||||
|   | ||||
| @@ -30,12 +30,13 @@ public static class FileServerHelpers | ||||
|  | ||||
|         var metadata = new Metadata();//传递到服务器的元数据 | ||||
|         metadata.Add(FileConst.FilePathKey, path); | ||||
|         using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300)); | ||||
|         var fileOperator = new FileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 | ||||
|         { | ||||
|             SavePath = savePath,//客户端本地保存路径 | ||||
|             ResourcePath = path,//请求文件的资源路径 | ||||
|             Metadata = metadata,//传递到服务器的元数据 | ||||
|             Timeout = TimeSpan.FromSeconds(60),//传输超时时长 | ||||
|             Token = cts.Token, | ||||
|             TryCount = 10,//当遇到失败时,尝试次数 | ||||
|             FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 | ||||
|         }; | ||||
| @@ -75,12 +76,13 @@ public static class FileServerHelpers | ||||
|         var metadata = new Metadata();//传递到服务器的元数据 | ||||
|         metadata.Add(FileConst.FilePathKey, serverPath); | ||||
|  | ||||
|         using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300)); | ||||
|         var fileOperator = new FileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等。 | ||||
|         { | ||||
|             SavePath = serverPath,//服务器本地保存路径 | ||||
|             ResourcePath = resourcePath,//客户端本地即将上传文件的资源路径 | ||||
|             Metadata = metadata,//传递到服务器的元数据 | ||||
|             Timeout = TimeSpan.FromSeconds(60),//传输超时时长 | ||||
|             Token = cts.Token, | ||||
|             TryCount = 10,//当遇到失败时,尝试次数 | ||||
|             FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值 | ||||
|         }; | ||||
|   | ||||
| @@ -11,18 +11,22 @@ | ||||
|  | ||||
| using TouchSocket.Dmtp.Rpc; | ||||
| using TouchSocket.Rpc; | ||||
| using TouchSocket.WebApi; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| #if Management | ||||
| [GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)] | ||||
| #endif | ||||
| [TouchSocket.WebApi.Router("/miniapi/upgrade/[action]")] | ||||
| public interface IUpgradeRpcServer : IRpcServer | ||||
| { | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Post)] | ||||
|     Task UpgradeAsync(ICallContext callContext, UpdateZipFile updateZipFile); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     [WebApi(Method = HttpMethodType.Get)] | ||||
|     Task<UpdateZipFileInput> GetUpdateZipFileInputAsync(ICallContext callContext); | ||||
|  | ||||
| } | ||||
| @@ -11,7 +11,6 @@ | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| @@ -89,6 +88,14 @@ public partial class WebApiTask : AsyncDisposableObject | ||||
|                        store.RegisterServer<ControlController>(); | ||||
|                        store.RegisterServer<RuntimeInfoController>(); | ||||
|                        store.RegisterServer<TestController>(); | ||||
|  | ||||
|                        store.RegisterServer<IManagementRpcServer>(new ManagementRpcServer()); | ||||
|                        store.RegisterServer<IUpgradeRpcServer>(new UpgradeRpcServer()); | ||||
|  | ||||
|                        foreach (var type in App.EffectiveTypes.Where(p => typeof(IPluginRpcServer).IsAssignableFrom(p) && !p.IsAbstract && p.IsClass)) | ||||
|                        { | ||||
|                            store.RegisterServer(type); | ||||
|                        } | ||||
|                    }); | ||||
|  | ||||
|                    //添加跨域服务 | ||||
| @@ -112,9 +119,12 @@ public partial class WebApiTask : AsyncDisposableObject | ||||
|  | ||||
|                    a.UseWebApi(); | ||||
|  | ||||
|                    if (App.WebHostEnvironment.IsDevelopment() || Debugger.IsAttached) | ||||
|                        a.UseSwagger(); | ||||
|  | ||||
| #if DEBUG | ||||
|                    a.UseSwagger().SetPrefix("api"); | ||||
| #else | ||||
|                    if (App.WebHostEnvironment.IsDevelopment()) | ||||
|                        a.UseSwagger().SetPrefix("api"); | ||||
| #endif | ||||
|                    a.UseDefaultHttpServicePlugin(); | ||||
|                }); | ||||
|  | ||||
| @@ -194,7 +204,6 @@ class AuthenticationPlugin : PluginBase, IHttpPlugin | ||||
|         var username = credentials[0]; | ||||
|         var password = credentials[1]; | ||||
|  | ||||
|         // 这里验证用户名和密码,实际项目中应该从数据库验证 | ||||
|         if (username != _webApiOptions.UserName || password != _webApiOptions.Password) | ||||
|         { | ||||
|             e.Context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"ThingsGateway\""); | ||||
|   | ||||
| @@ -53,6 +53,7 @@ public class AlarmChangedTriggerNode : VariableNode, ITriggerNode, IDisposable | ||||
|         GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a => AlarmHostedService_OnAlarmChanged(a.Value)); | ||||
|         GlobalData.AlarmChangedEvent += AlarmHostedService_OnAlarmChanged; | ||||
|     } | ||||
|  | ||||
|     private static void AlarmHostedService_OnAlarmChanged(AlarmVariable alarmVariable) | ||||
|     { | ||||
|         if (AlarmChangedTriggerNodeDict.TryGetValue(alarmVariable.DeviceName, out var alarmNodeDict) && | ||||
|   | ||||
| @@ -39,6 +39,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable | ||||
|  | ||||
|     static DeviceChangedTriggerNode() | ||||
|     { | ||||
|         GlobalData.DeviceStatusChangeEvent -= GlobalData_DeviceStatusChangeEvent; | ||||
|         Task.Factory.StartNew(RunAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); | ||||
|         GlobalData.DeviceStatusChangeEvent += GlobalData_DeviceStatusChangeEvent; | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,112 @@ | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| using ThingsGateway.Blazor.Diagrams.Core.Geometry; | ||||
| using ThingsGateway.Common.Extension; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| [CategoryNode(Category = "Trigger", ImgUrl = $"{INode.ModuleBasePath}img/ValueChanged.svg", Desc = nameof(PluginEventChangedTriggerNode), LocalizerType = typeof(ThingsGateway.Gateway.Application.INode), WidgetType = $"ThingsGateway.Gateway.Razor.DeviceWidget,{INode.FileModulePath}")] | ||||
| public class PluginEventChangedTriggerNode : TextNode, ITriggerNode, IDisposable | ||||
| { | ||||
|     public PluginEventChangedTriggerNode(string id, Point? position = null) : base(id, position) { Title = "PluginEventChangedTriggerNode"; Placeholder = "Device.Placeholder"; } | ||||
|  | ||||
|  | ||||
| #if !Management | ||||
|     private Func<NodeOutput, CancellationToken, Task> Func { get; set; } | ||||
|     Task ITriggerNode.StartAsync(Func<NodeOutput, CancellationToken, Task> func, CancellationToken cancellationToken) | ||||
|     { | ||||
|         Func = func; | ||||
|         FuncDict.Add(this, func); | ||||
|         if (!DeviceChangedTriggerNodeDict.TryGetValue(Text ?? string.Empty, out var list)) | ||||
|         { | ||||
|             var deviceChangedTriggerNodes = new ConcurrentList<PluginEventChangedTriggerNode>(); | ||||
|             deviceChangedTriggerNodes.Add(this); | ||||
|             DeviceChangedTriggerNodeDict.Add(Text, deviceChangedTriggerNodes); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             list.Add(this); | ||||
|         } | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|     public static Dictionary<string, ConcurrentList<PluginEventChangedTriggerNode>> DeviceChangedTriggerNodeDict = new(); | ||||
|     public static Dictionary<PluginEventChangedTriggerNode, Func<NodeOutput, CancellationToken, Task>> FuncDict = new(); | ||||
|  | ||||
|     public static BlockingCollection<PluginEventData> DeviceDatas = new(); | ||||
|  | ||||
|     static PluginEventChangedTriggerNode() | ||||
|     { | ||||
|         GlobalData.PluginEventHandler -= GlobalData_PluginEventHandler; | ||||
|         Task.Factory.StartNew(RunAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); | ||||
|         GlobalData.PluginEventHandler += GlobalData_PluginEventHandler; | ||||
|     } | ||||
|  | ||||
|     private static void GlobalData_PluginEventHandler(PluginEventData pluginEventData) | ||||
|     { | ||||
|         if (DeviceChangedTriggerNodeDict.TryGetValue(pluginEventData.DeviceName ?? string.Empty, out var deviceChangedTriggerNodes) && deviceChangedTriggerNodes?.Count > 0) | ||||
|         { | ||||
|             if (!DeviceDatas.IsAddingCompleted) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     DeviceDatas.Add(pluginEventData); | ||||
|                     return; | ||||
|                 } | ||||
|                 catch (InvalidOperationException) { } | ||||
|                 catch { } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     static Task RunAsync() | ||||
|     { | ||||
|         return DeviceDatas.GetConsumingEnumerable().ParallelForEachStreamedAsync((async (plgunEventData, token) => | ||||
|         { | ||||
|             if (DeviceChangedTriggerNodeDict.TryGetValue(plgunEventData.DeviceName ?? string.Empty, out var valueChangedTriggerNodes)) | ||||
|             { | ||||
|                 await valueChangedTriggerNodes.ParallelForEachAsync(async (item, token) => | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         if (FuncDict.TryGetValue(item, out var func)) | ||||
|                         { | ||||
|                             item.Logger?.Trace($"Device changed: {item.Text}"); | ||||
|                             await func.Invoke(new NodeOutput() { Value = plgunEventData }, token).ConfigureAwait(false); | ||||
|                         } | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         item.Logger?.LogWarning(ex); | ||||
|                     } | ||||
|                 }, token).ConfigureAwait(false); | ||||
|             } | ||||
|         }), default); | ||||
|     } | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|         FuncDict.Remove(this); | ||||
|         if (DeviceChangedTriggerNodeDict.TryGetValue(Text ?? string.Empty, out var list)) | ||||
|         { | ||||
|             list.Remove(this); | ||||
|         } | ||||
|     } | ||||
| #else | ||||
|  | ||||
|     Task ITriggerNode.StartAsync(Func<NodeOutput, CancellationToken, Task> func, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|  | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| } | ||||
| @@ -46,6 +46,7 @@ public class ValueChangedTriggerNode : VariableNode, ITriggerNode, IDisposable | ||||
|     public static BlockingCollection<VariableBasicData> VariableBasicDatas = new(); | ||||
|     static ValueChangedTriggerNode() | ||||
|     { | ||||
|         GlobalData.VariableValueChangeEvent -= GlobalData_VariableValueChangeEvent; | ||||
|         Task.Factory.StartNew(RunAsync, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); | ||||
|         GlobalData.VariableValueChangeEvent += GlobalData_VariableValueChangeEvent; | ||||
|     } | ||||
|   | ||||
| @@ -29,7 +29,7 @@ public interface IRulesService | ||||
|     /// 从缓存/数据库获取全部信息 | ||||
|     /// </summary> | ||||
|     /// <returns>规则列表</returns> | ||||
|     Task<List<Rules>> GetAllAsync(); | ||||
|     Task<List<Rules>> GetAllRulesAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 报表查询 | ||||
|   | ||||
| @@ -240,7 +240,7 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine | ||||
|             TokenSource ??= new CancellationTokenSource(); | ||||
|             Clear(); | ||||
|  | ||||
|             Rules = await App.GetService<IRulesService>().GetAllAsync().ConfigureAwait(false); | ||||
|             Rules = await App.GetService<IRulesService>().GetAllRulesAsync().ConfigureAwait(false); | ||||
|             Diagrams = new(); | ||||
|             foreach (var rules in Rules.Where(a => a.Status)) | ||||
|             { | ||||
|   | ||||
| @@ -39,7 +39,7 @@ internal sealed class RulesService : BaseService<Rules>, IRulesService | ||||
|  | ||||
|         using var db = GetDB(); | ||||
|  | ||||
|         var data = (await GetAllAsync().ConfigureAwait(false)) | ||||
|         var data = (await GetAllRulesAsync().ConfigureAwait(false)) | ||||
|               .WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询 | ||||
|             .WhereIf(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId) | ||||
|             .Select(a => a.Id).ToList(); | ||||
| @@ -75,7 +75,7 @@ internal sealed class RulesService : BaseService<Rules>, IRulesService | ||||
|     /// 从缓存/数据库获取全部信息 | ||||
|     /// </summary> | ||||
|     /// <returns>列表</returns> | ||||
|     public async Task<List<Rules>> GetAllAsync() | ||||
|     public async Task<List<Rules>> GetAllRulesAsync() | ||||
|     { | ||||
|         var channels = App.CacheService.Get<List<Rules>>(cacheKey); | ||||
|         if (channels == null) | ||||
|   | ||||
| @@ -11,8 +11,8 @@ | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.1" /> | ||||
| 		<PackageReference Include="System.Linq.Async" Version="6.0.3" /> | ||||
| 		<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.1" /> | ||||
| 		<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.1" /> | ||||
| 		<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.5" /> | ||||
| 		<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.5" /> | ||||
| 		<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" /> | ||||
| 		<!--<ProjectReference Include="..\..\PluginPro\ThingsGateway.Authentication\ThingsGateway.Authentication.csproj" />--> | ||||
|  | ||||
|   | ||||
| @@ -73,12 +73,22 @@ public partial class QuickActions | ||||
|  | ||||
|     private List<SelectedItem> AutoRestartThreadBoolItems; | ||||
|  | ||||
|     private static async Task Restart() | ||||
|     [Inject] | ||||
|     IChannelPageService ChannelPageService { get; set; } | ||||
|  | ||||
| #if Management | ||||
|     [Inject] | ||||
|     Management.Application.DmtpActorContext DmtpActorContext { get; set; } | ||||
| #endif | ||||
|     private async Task Restart() | ||||
|     { | ||||
| #if Management | ||||
|         if (DmtpActorContext.Current == null) | ||||
|             return; | ||||
| #endif | ||||
|         await Task.Run(async () => | ||||
|         { | ||||
|             var data = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false); | ||||
|             await GlobalData.ChannelRuntimeService.RestartChannelAsync(data.ToList()); | ||||
|             await ChannelPageService.RestartChannelsAsync(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -55,6 +55,7 @@ | ||||
|  | ||||
|     "Actuator": "Actuator", | ||||
|     "AlarmChangedTriggerNode": "AlarmStateTrigger", | ||||
|     "PluginEventChangedTriggerNode": "PluginEventTrigger", | ||||
|     "BusinessNode": "BusinessDeviceExecution", | ||||
|     "BusinessNode.Placeholder": "BusinessDeviceName", | ||||
|     "Cancel": "Cancel", | ||||
|   | ||||
| @@ -55,6 +55,7 @@ | ||||
|  | ||||
|     "Actuator": "执行", | ||||
|     "AlarmChangedTriggerNode": "报警状态触发器", | ||||
|     "PluginEventChangedTriggerNode": "插件事件触发器", | ||||
|     "BusinessNode": "业务设备执行", | ||||
|     "BusinessNode.Placeholder": "设备名称", | ||||
|     "Cancel": "取消", | ||||
|   | ||||
| @@ -8,8 +8,12 @@ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Text; | ||||
|  | ||||
| using ThingsGateway.NewLife.Json.Extension; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Razor; | ||||
| public partial class PropertyComponent : IPropertyUIBase | ||||
| { | ||||
| @@ -69,11 +73,16 @@ public partial class PropertyComponent : IPropertyUIBase | ||||
|         {nameof(ScriptCheck.Script),script }, | ||||
|         {nameof(ScriptCheck.GetResult), (string input,string script)=> | ||||
|         { | ||||
|                 StringBuilder stringBuilder=new(); | ||||
|                var logger=new EasyLogger((a)=>stringBuilder.AppendLine(a)); | ||||
|  | ||||
|  | ||||
|                 var type=  script == businessProperty.BigTextScriptDeviceModel?typeof(List<DeviceBasicData>):script ==businessProperty.BigTextScriptAlarmModel?typeof(List<AlarmVariable>):typeof(List<VariableBasicData>); | ||||
|  | ||||
|                 var  data = (IEnumerable<object>)Newtonsoft.Json.JsonConvert.DeserializeObject(input, type); | ||||
|                 var value = data.GetDynamicModel(script); | ||||
|               return Task.FromResult( value.ToSystemTextJsonString()); | ||||
|                 var value = data.GetDynamicModel(script,logger); | ||||
|                 stringBuilder.AppendLine(value.ToSystemTextJsonString()); | ||||
|               return Task.FromResult( stringBuilder.ToString()); | ||||
|         }}, | ||||
|  | ||||
|         {nameof(ScriptCheck.OnGetDemo),()=> | ||||
|   | ||||
| @@ -77,7 +77,7 @@ | ||||
|                     <EditTemplate Context="value"> | ||||
|                         <div class="col-12  col-md-6 "> | ||||
|                             <BootstrapInputGroup> | ||||
|                                 <Select IsVirtualize @bind-Value="@value.DeviceId" DefaultVirtualizeItemText=@DeviceName  IsDisabled=BatchEditEnable Items="@CollectDeviceItems" OnSelectedItemChanged=OnDeviceChanged ShowSearch="true" ShowLabel="true" /> | ||||
|                                 <Select IsVirtualize @bind-Value="@value.DeviceId" DefaultVirtualizeItemText=@DeviceName  IsDisabled=BatchEditEnable OnQueryAsync=OnCollectDeviceSelectedItemQueryAsync OnSelectedItemChanged=OnDeviceChanged ShowSearch="true" ShowLabel="true" /> | ||||
|                                 <Button class="text-end" Icon="fa-solid fa-plus" OnClick="AddDevice" IsDisabled=BatchEditEnable></Button> | ||||
|                             </BootstrapInputGroup> | ||||
|                         </div> | ||||
| @@ -198,7 +198,7 @@ | ||||
|                 <div class="row g-2 mx-1 form-inline"> | ||||
|  | ||||
|                     <div class="col-12 col-md-8"> | ||||
|                     <Select SkipValidate IsVirtualize DefaultVirtualizeItemText=@(ChoiceBusinessDeviceName) @bind-Value="@ChoiceBusinessDeviceId" Items="@BusinessDeviceItems" ShowSearch="true" ShowLabel="true" /> | ||||
|                             <Select SkipValidate IsVirtualize @bind-Value="@ChoiceBusinessDeviceId" DefaultVirtualizeItemText=@ChoiceBusinessDeviceName IsDisabled=BatchEditEnable OnQueryAsync=OnBusinessDeviceSelectedItemQueryAsync ShowSearch="true" ShowLabel="true" /> | ||||
|                     </div> | ||||
|                     <div class="col-12 col-md-4"> | ||||
|                         <Button OnClick="async() =>{ | ||||
|   | ||||
| @@ -41,9 +41,9 @@ public partial class VariableEditComponent | ||||
|     [Parameter] | ||||
|     public bool BatchEditEnable { get; set; } | ||||
|  | ||||
|     private IEnumerable<SelectedItem> BusinessDeviceItems { get; set; } | ||||
|     //private IEnumerable<SelectedItem> BusinessDeviceItems { get; set; } | ||||
|  | ||||
|     private IEnumerable<SelectedItem> CollectDeviceItems { get; set; } | ||||
|     //private IEnumerable<SelectedItem> CollectDeviceItems { get; set; } | ||||
|     [Inject] | ||||
|     IDevicePageService DevicePageService { get; set; } | ||||
|     private string DeviceName; | ||||
| @@ -59,10 +59,14 @@ public partial class VariableEditComponent | ||||
|             OnInitialized(); | ||||
|             await OnInitializedAsync(); | ||||
|             OnParametersSet(); | ||||
|             StateHasChanged(); | ||||
|  | ||||
|             ChoiceBusinessDeviceId = ChoiceBusinessDeviceId > 0 ? ChoiceBusinessDeviceId : (await DevicePageService.OnDeviceSelectedItemQueryAsync(new VirtualizeQueryOption() { Count = 1 }, false).ConfigureAwait(false)).Items.FirstOrDefault()?.Value?.ToLong() ?? 0; | ||||
|             ChoiceBusinessDeviceName = (await DevicePageService.GetDeviceNameAsync(ChoiceBusinessDeviceId)) ?? string.Empty; | ||||
|  | ||||
|             await InvokeAsync(StateHasChanged); | ||||
|  | ||||
|             await OnParametersSetAsync(); | ||||
|             ChoiceBusinessDeviceId = ChoiceBusinessDeviceId > 0 ? ChoiceBusinessDeviceId : BusinessDeviceItems.FirstOrDefault()?.Value?.ToLong() ?? 0; | ||||
|             ChoiceBusinessDeviceName = await DevicePageService.GetDeviceNameAsync(ChoiceBusinessDeviceId); | ||||
|  | ||||
|         } | ||||
|         else | ||||
|         { | ||||
| @@ -72,8 +76,6 @@ public partial class VariableEditComponent | ||||
|  | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         CollectDeviceItems = await DevicePageService.GetDeviceItemsAsync(true); | ||||
|         BusinessDeviceItems = await DevicePageService.GetDeviceItemsAsync(false); | ||||
|  | ||||
|         if (Model.DeviceId > 0 && AddressUIType == null) | ||||
|         { | ||||
| @@ -82,6 +84,14 @@ public partial class VariableEditComponent | ||||
|  | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
|     private Task<QueryData<SelectedItem>> OnCollectDeviceSelectedItemQueryAsync(VirtualizeQueryOption option) | ||||
|     { | ||||
|         return DevicePageService.OnDeviceSelectedItemQueryAsync(option, true); | ||||
|     } | ||||
|     private Task<QueryData<SelectedItem>> OnBusinessDeviceSelectedItemQueryAsync(VirtualizeQueryOption option) | ||||
|     { | ||||
|         return DevicePageService.OnDeviceSelectedItemQueryAsync(option, false); | ||||
|     } | ||||
|  | ||||
|     [Parameter] | ||||
|     [EditorRequired] | ||||
|   | ||||
| @@ -111,9 +111,12 @@ | ||||
|       { | ||||
|         "Url": "/InovanceMaster", | ||||
|         "Text": "InovanceMaster" | ||||
|       }, | ||||
|       { | ||||
|         "Url": "/OpcAeMaster", | ||||
|         "Text": "OpcAeMaster" | ||||
|       } | ||||
|  | ||||
|  | ||||
|     ] | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -189,7 +189,7 @@ public class Dlt645_2007Send : ISendMessage | ||||
|         lenSpan.WriteValue<byte>((byte)(length - 1));//数据域长度 | ||||
|  | ||||
|         int num = 0; | ||||
|         for (int index = 0; index < byteBlock.WrittenCount; ++index) | ||||
|         for (int index = 0; index < byteBlock.WrittenCount - SendHeadCodeIndex; ++index) | ||||
|             num += span[index]; | ||||
|         WriterExtension.WriteValue(ref byteBlock, (byte)num);//校验码,总加和 | ||||
|         WriterExtension.WriteValue(ref byteBlock, (byte)0x16);//结束符 | ||||
|   | ||||
| @@ -144,6 +144,7 @@ public class S7Send : ISendMessage | ||||
|  | ||||
|     internal void GetWriteByteCommand<TByteBlock>(ref TByteBlock byteBlock, SiemensS7Address[] addresss) where TByteBlock : IBytesWriter | ||||
|     { | ||||
|         var span = byteBlock.GetSpan(1024); | ||||
|         byte itemLen = (byte)addresss.Length; | ||||
|         ushort parameterLen = (ushort)(itemLen * 12 + 2); | ||||
|         //TPKT | ||||
| @@ -201,7 +202,10 @@ public class S7Send : ISendMessage | ||||
|         } | ||||
|         ushort telegramLen = (ushort)(itemLen * 12 + 19 + dataLen); | ||||
|  | ||||
|         ByteBlockExtension.WriteBackValue(ref byteBlock, (ushort)telegramLen, EndianType.Big, 2);//长度 | ||||
|         ByteBlockExtension.WriteBackValue(ref byteBlock, (ushort)dataLen, EndianType.Big, 15);//长度 | ||||
|         TouchSocketBitConverter.GetBitConverter(EndianType.Big).WriteBytes(span.Slice(2), (ushort)telegramLen); | ||||
|         //ByteBlockExtension.WriteBackValue(ref byteBlock, (ushort)telegramLen, EndianType.Big, 2);//长度 | ||||
|         TouchSocketBitConverter.GetBitConverter(EndianType.Big).WriteBytes(span.Slice(15), (ushort)dataLen); | ||||
|  | ||||
|         //ByteBlockExtension.WriteBackValue(ref byteBlock, (ushort)dataLen, EndianType.Big, 15);//长度 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -22,7 +22,7 @@ public abstract class DynamicSQLBase | ||||
|     /// <returns></returns> | ||||
|     public virtual Task DBInit(ISqlSugarClient db, CancellationToken cancellationToken) | ||||
|     { | ||||
|         throw new NotSupportedException(); | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
| @@ -1,60 +0,0 @@ | ||||
| ////------------------------------------------------------------------------------ | ||||
| ////此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //// 此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //// 源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //// 使用文档:https://thingsgateway.cn/ | ||||
| //// QQ群:605534569 | ||||
| //// ------------------------------------------------------------------------------ | ||||
|  | ||||
| //using ThingsGateway.SqlSugar; | ||||
|  | ||||
| //using ThingsGateway; | ||||
| //using ThingsGateway.Foundation; | ||||
| //using ThingsGateway.NewLife.Extension; | ||||
| //using ThingsGateway.Plugin.DB; | ||||
| //using ThingsGateway.Plugin.SqlDB; | ||||
|  | ||||
| //public class TestHistorySQL : DynamicSQLBase | ||||
| //{ | ||||
| //    public override IEnumerable<dynamic> GetList(IEnumerable<object> datas) | ||||
| //    { | ||||
| //        var sqlHistoryValues = datas.Cast<SQLHistoryValue>().OrderByDescending(a => a.CollectTime).DistinctBy(a => a.Name); | ||||
| //        List<HistoryModel1> demoDatas = new List<HistoryModel1>(); | ||||
| //        HistoryModel1 demoData = new HistoryModel1(); | ||||
| //        demoData.IsOnline = !sqlHistoryValues.Any(a => !a.IsOnline); | ||||
| //        demoData.CreateTime = DateTime.Now; | ||||
| //        var dict = sqlHistoryValues.ToDictionary(a => a.Name); | ||||
| //        demoData.Temp1 = dict["Device1_Temp1"].Value; | ||||
| //        demoData.Temp2 = dict["Device1_Temp2"].Value; | ||||
| //        demoData.Temp3 = dict["Device1_Temp3"].Value; | ||||
| //        demoDatas.Add(demoData); | ||||
| //        return demoDatas; | ||||
| //    } | ||||
|  | ||||
| //    public override Type GetModelType() | ||||
| //    { | ||||
| //        return typeof(HistoryModel1); | ||||
| //    } | ||||
| //    [SplitTable(SplitType.Month)]//(自带分表支持 年、季、月、周、日) | ||||
| //    [SugarTable("HistoryModel1_{year}{month}{day}", TableDescription = "设备采集历史表")]//3个变量必须要有 | ||||
| //    [SugarIndex("index_CreateTime", nameof(SQLHistoryValue.CreateTime), OrderByType.Desc)] | ||||
| //    public class HistoryModel1 | ||||
| //    { | ||||
| //        [SplitField] //分表字段 在插入的时候会根据这个字段插入哪个表,在更新删除的时候用这个字段找出相关表 | ||||
| //        public DateTime CreateTime { get; set; } | ||||
|  | ||||
| //        ///<summary> | ||||
| //        ///是否在线 | ||||
| //        ///</summary> | ||||
| //        [SugarColumn(ColumnDescription = "是否在线")] | ||||
| //        public bool IsOnline { get; set; } | ||||
|  | ||||
| //        public string Temp1 { get; set; } | ||||
|  | ||||
| //        public string Temp2 { get; set; } | ||||
|  | ||||
| //        public string Temp3 { get; set; } | ||||
| //    } | ||||
| //} | ||||
| @@ -1,65 +0,0 @@ | ||||
| //// ------------------------------------------------------------------------------ | ||||
| //// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //// 此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //// 源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //// 使用文档:https://thingsgateway.cn/ | ||||
| //// QQ群:605534569 | ||||
| //// ------------------------------------------------------------------------------ | ||||
|  | ||||
| //using ThingsGateway.SqlSugar; | ||||
|  | ||||
| //using ThingsGateway; | ||||
| //using ThingsGateway.Foundation; | ||||
| //using ThingsGateway.NewLife.Extension; | ||||
| //using ThingsGateway.Plugin.DB; | ||||
| //using ThingsGateway.Plugin.SqlDB; | ||||
|  | ||||
| //public class TestRealSQL : DynamicSQLBase | ||||
| //{ | ||||
| //    public override IEnumerable<dynamic> GetList(IEnumerable<object> datas) | ||||
| //    { | ||||
| //        var sqlRealValues = datas.Cast<SQLRealValue>().OrderByDescending(a => a.CollectTime).DistinctBy(a => a.Name); | ||||
| //        List<RealModel1> demoDatas = new List<RealModel1>(); | ||||
| //        RealModel1 demoData = new RealModel1(); | ||||
| //        demoData.IsOnline = !sqlRealValues.Any(a => !a.IsOnline); | ||||
| //        demoData.CollectTime = sqlRealValues.Select(a => a.CollectTime).Max(); | ||||
| //        demoData.EmptyId = 1; | ||||
| //        var dict = sqlRealValues.ToDictionary(a => a.Name); | ||||
| //        demoData.Temp1 = dict["Device1_Temp1"].Value; | ||||
| //        demoData.Temp2 = dict["Device1_Temp2"].Value; | ||||
| //        demoData.Temp3 = dict["Device1_Temp3"].Value; | ||||
| //        demoDatas.Add(demoData); | ||||
| //        return demoDatas; | ||||
| //    } | ||||
|  | ||||
| //    public override Type GetModelType() | ||||
| //    { | ||||
| //        return typeof(RealModel1); | ||||
|  | ||||
| //    } | ||||
|  | ||||
| //    [SugarTable(TableName = "RealModel1", TableDescription = "设备采集实时表")] | ||||
| //    [SugarIndex("{table}_index_CollectTime", nameof(SQLRealValue.CollectTime), OrderByType.Desc)] | ||||
| //    public class RealModel1 | ||||
| //    { | ||||
| //        [SugarColumn(IsPrimaryKey = true)] | ||||
| //        public long EmptyId { get; set; } | ||||
|  | ||||
| //        [SugarColumn(ColumnDescription = "采集时间")] | ||||
| //        public DateTime CollectTime { get; set; } | ||||
|  | ||||
| //        ///<summary> | ||||
| //        ///是否在线 | ||||
| //        ///</summary> | ||||
| //        [SugarColumn(ColumnDescription = "是否在线")] | ||||
| //        public bool IsOnline { get; set; } | ||||
|  | ||||
| //        public string Temp1 { get; set; } | ||||
|  | ||||
| //        public string Temp2 { get; set; } | ||||
|  | ||||
| //        public string Temp3 { get; set; } | ||||
| //    } | ||||
| //} | ||||
| @@ -64,7 +64,7 @@ | ||||
|     "Name": "Name", | ||||
|     "Value": "Value" | ||||
|   }, | ||||
|   "ThingsGateway.Plugin.SqlHistoryAlarm.HistoryAlarm": { | ||||
|   "ThingsGateway.Plugin.DB.HistoryAlarm": { | ||||
|     "AlarmCode": "AlarmCode", | ||||
|     "AlarmLimit": "AlarmLimit", | ||||
|     "AlarmText": "AlarmText", | ||||
| @@ -86,10 +86,14 @@ | ||||
|     "Remark4": "Remark4", | ||||
|     "Remark5": "Remark5" | ||||
|   }, | ||||
|   "ThingsGateway.Plugin.SqlHistoryAlarm.SQLHistoryAlarmProperty": { | ||||
|   "ThingsGateway.Plugin.DB.SQLHistoryAlarmProperty": { | ||||
|     "BigTextConnectStr": "ConnectString", | ||||
|     "DbType": "DbType", | ||||
|     "TableName": "TableName" | ||||
|     "TableName": "TableName", | ||||
|     "VariableAlarmEnable": "VariableAlarmEnable", | ||||
|     "PluginEventEnable": "PluginEventEnable", | ||||
|     "BigTextScriptHistoryTable": "BigTextScriptHistoryTable", | ||||
|     "BigTextScriptPluginEventDataHistoryTable": "BigTextScriptPluginEventDataHistoryTable" | ||||
|   }, | ||||
|   "ThingsGateway.Plugin.TDengineDB.TDengineDBNumberHistoryValue": { | ||||
|     "CollectTime": "CollectTime", | ||||
|   | ||||
| @@ -64,7 +64,7 @@ | ||||
|     "Name": "变量名称", | ||||
|     "Value": "变量值" | ||||
|   }, | ||||
|   "ThingsGateway.Plugin.SqlHistoryAlarm.HistoryAlarm": { | ||||
|   "ThingsGateway.Plugin.DB.HistoryAlarm": { | ||||
|     "AlarmCode": "报警值", | ||||
|     "AlarmLimit": "报警限值", | ||||
|     "AlarmText": "报警文本", | ||||
| @@ -86,10 +86,14 @@ | ||||
|     "Remark4": "备用4", | ||||
|     "Remark5": "备用5" | ||||
|   }, | ||||
|   "ThingsGateway.Plugin.SqlHistoryAlarm.SQLHistoryAlarmProperty": { | ||||
|   "ThingsGateway.Plugin.DB.SQLHistoryAlarmProperty": { | ||||
|     "BigTextConnectStr": "链接字符串", | ||||
|     "DbType": "数据库类型", | ||||
|     "TableName": "表名称" | ||||
|     "TableName": "表名称", | ||||
|     "VariableAlarmEnable": "变量报警存储", | ||||
|     "PluginEventEnable": "插件事件存储", | ||||
|     "BigTextScriptHistoryTable": "变量报警脚本", | ||||
|     "BigTextScriptPluginEventDataHistoryTable": "插件事件脚本" | ||||
|   }, | ||||
|   "ThingsGateway.Plugin.TDengineDB.TDengineDBNumberHistoryValue": { | ||||
|     "CollectTime": "采集时间", | ||||
|   | ||||
| @@ -11,7 +11,6 @@ | ||||
| using Riok.Mapperly.Abstractions; | ||||
|  | ||||
| using ThingsGateway.DB; | ||||
| using ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.DB; | ||||
| [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)] | ||||
|   | ||||
| @@ -10,6 +10,6 @@ | ||||
|  | ||||
| namespace ThingsGateway.Plugin.QuestDB; | ||||
|  | ||||
| public class QuestDBProducerVariableProperty : VariablePropertyBase | ||||
| public class QuestDBProducerVariableProperty : BusinessVariableProperty | ||||
| { | ||||
| } | ||||
| @@ -15,19 +15,19 @@ | ||||
|               @ref=Model.ValidateForm | ||||
|               Id=@($"DeviceEditValidateForm{Id}{Model.Value.GetType().TypeHandle.Value}")> | ||||
|  | ||||
|     <EditorFormObject class="p-2" Items=PluginPropertyEditorItems IsDisplay="!CanWrite" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=@(CanWrite?2:3) ShowLabelTooltip=true LabelWidth=@(CanWrite?240:120) Model="Model.Value" ShowLabel="true" @key=@($"DeviceEditEditorFormObject{Id}{Model.Value.GetType().TypeHandle.Value}")> | ||||
|     <EditorFormObject class="p-2" Items=PluginPropertyEditorItems IsDisplay="!CanWrite" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=@(CanWrite ? 2 : 3) ShowLabelTooltip=true LabelWidth=@(CanWrite ? 240 : 120) Model="Model.Value" ShowLabel="true" @key=@($"DeviceEditEditorFormObject{Id}{Model.Value.GetType().TypeHandle.Value}")> | ||||
|  | ||||
|         <FieldItems> | ||||
|             @if (Model.Value is SqlDBProducerProperty businessProperty) | ||||
|             { | ||||
|                 <EditorItem FieldExpression=@(()=>context) Field=@(context)> | ||||
|                 <EditorItem FieldExpression=@(() => context) Field=@(context)> | ||||
|  | ||||
|                     <EditTemplate Context="value"> | ||||
|                         <div class="col-12  col-md-12"> | ||||
|                             <BootstrapLabel Value=@ProducerPropertyLocalizer["BigTextScriptHistoryTable"] ShowLabelTooltip="true" /> | ||||
|                             <CodeEditor @bind-Value=@businessProperty.BigTextScriptHistoryTable Language="csharp" Theme="vs-dark" /> | ||||
|                             <div class="ms-2 d-flex justify-content-center align-items-center"> | ||||
|                                 <Button IsDisabled=@(!CanWrite) OnClick="()=>CheckScript(businessProperty,nameof(businessProperty.BigTextScriptHistoryTable))"> | ||||
|                                 <Button IsDisabled=@(!CanWrite) OnClick="() => CheckScript(businessProperty, nameof(businessProperty.BigTextScriptHistoryTable))"> | ||||
|                                     @RazorLocalizer["Check"] | ||||
|                                 </Button> | ||||
|                             </div> | ||||
| @@ -36,7 +36,7 @@ | ||||
|                             <BootstrapLabel Value=@ProducerPropertyLocalizer["BigTextScriptRealTable"] ShowLabelTooltip="true" /> | ||||
|                             <CodeEditor @bind-Value=@businessProperty.BigTextScriptRealTable Language="csharp" Theme="vs-dark" /> | ||||
|                             <div class="ms-2 d-flex justify-content-center align-items-center"> | ||||
|                                 <Button IsDisabled=@(!CanWrite) OnClick="()=>CheckScript(businessProperty,nameof(businessProperty.BigTextScriptRealTable))"> | ||||
|                                 <Button IsDisabled=@(!CanWrite) OnClick="() => CheckScript(businessProperty, nameof(businessProperty.BigTextScriptRealTable))"> | ||||
|                                     @RazorLocalizer["Check"] | ||||
|                                 </Button> | ||||
|                             </div> | ||||
| @@ -62,7 +62,22 @@ | ||||
|                     </EditTemplate> | ||||
|                 </EditorItem> | ||||
|             } | ||||
|             else if (Model.Value is SqlHistoryAlarmProperty sqlHistoryAlarmProperty) | ||||
|             { | ||||
|                 <EditorItem FieldExpression=@(() => context) Field=@(context)> | ||||
|  | ||||
|                     <EditTemplate Context="value"> | ||||
|                         <div class="col-12  col-md-12"> | ||||
|                             <BootstrapLabel Value=@ProducerPropertyLocalizer["BigTextScriptHistoryTable"] ShowLabelTooltip="true" /> | ||||
|                             <CodeEditor @bind-Value=@sqlHistoryAlarmProperty.BigTextScriptHistoryTable Language="csharp" Theme="vs-dark" /> | ||||
|                         </div> | ||||
|                         <div class="col-12  col-md-12"> | ||||
|                             <BootstrapLabel Value=@ProducerPropertyLocalizer["BigTextScriptPluginEventDataHistoryTable"] ShowLabelTooltip="true" /> | ||||
|                             <CodeEditor @bind-Value=@sqlHistoryAlarmProperty.BigTextScriptPluginEventDataHistoryTable Language="csharp" Theme="vs-dark" /> | ||||
|                         </div> | ||||
|                     </EditTemplate> | ||||
|                 </EditorItem> | ||||
|             } | ||||
|         </FieldItems> | ||||
|     </EditorFormObject> | ||||
| </ValidateForm> | ||||
|   | ||||
| @@ -13,6 +13,6 @@ namespace ThingsGateway.Plugin.SqlDB; | ||||
| /// <summary> | ||||
| /// SqlDBProducerVariableProperty | ||||
| /// </summary> | ||||
| public class SqlDBProducerVariableProperty : VariablePropertyBase | ||||
| public class SqlDBProducerVariableProperty : BusinessVariableProperty | ||||
| { | ||||
| } | ||||
| @@ -12,7 +12,7 @@ using BootstrapBlazor.Components; | ||||
|  | ||||
| using ThingsGateway.Extension.Generic; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
| namespace ThingsGateway.Plugin.DB; | ||||
|  | ||||
| public class HistoryAlarmPageInput : ITableSearchModel | ||||
| { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|  | ||||
| using ThingsGateway.SqlSugar; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
| namespace ThingsGateway.Plugin.DB; | ||||
|  | ||||
| [SugarTable("historyAlarm", TableDescription = "历史报警表")] | ||||
| public class HistoryAlarm : AlarmVariable | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| @using ThingsGateway.Admin.Razor | ||||
| @using ThingsGateway.Extension | ||||
| @using ThingsGateway.Gateway.Application | ||||
| @namespace ThingsGateway.Plugin.SqlHistoryAlarm | ||||
| @namespace ThingsGateway.Plugin.DB | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| using ThingsGateway.Common; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
| namespace ThingsGateway.Plugin.DB; | ||||
|  | ||||
| /// <summary> | ||||
| /// HistoryAlarmPage | ||||
|   | ||||
| @@ -12,12 +12,13 @@ using BootstrapBlazor.Components; | ||||
|  | ||||
| using ThingsGateway.Common; | ||||
| using ThingsGateway.DB; | ||||
| using ThingsGateway.Debug; | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Extension; | ||||
| using ThingsGateway.SqlSugar; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
| namespace ThingsGateway.Plugin.DB; | ||||
|  | ||||
| /// <summary> | ||||
| /// SqlHistoryAlarm | ||||
| @@ -55,7 +56,7 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm | ||||
|  | ||||
|     private SqlSugarClient _db; | ||||
|  | ||||
|  | ||||
|     public override Type DriverPropertyUIType => typeof(SqlDBProducerPropertyRazor); | ||||
|  | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
| @@ -66,6 +67,7 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm | ||||
| #if !Management | ||||
|     protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken) | ||||
|     { | ||||
|  | ||||
|         _db = BusinessDatabaseUtil.GetDb((DbType)_driverPropertys.DbType, _driverPropertys.BigTextConnectStr); | ||||
|  | ||||
|         await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false); | ||||
| @@ -76,11 +78,33 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public override bool IsConnected() => success; | ||||
|     protected override Task ProtectedStartAsync(CancellationToken cancellationToken) | ||||
|     protected override async Task ProtectedStartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _db.DbMaintenance.CreateDatabase(); | ||||
|         if (_driverPropertys.VariableAlarmEnable) | ||||
|         { | ||||
|             if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty()) | ||||
|             { | ||||
|                 var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable); | ||||
|  | ||||
|                 await hisModel.DBInit(_db, cancellationToken).ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _db.CodeFirst.AS<HistoryAlarm>(_driverPropertys.TableName).InitTables<HistoryAlarm>(); | ||||
|         return base.ProtectedStartAsync(cancellationToken); | ||||
|             } | ||||
|         } | ||||
|         if (_driverPropertys.PluginEventEnable) | ||||
|         { | ||||
|             if (!_driverPropertys.BigTextScriptPluginEventDataHistoryTable.IsNullOrEmpty()) | ||||
|             { | ||||
|                 var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptPluginEventDataHistoryTable); | ||||
|  | ||||
|                 await hisModel.DBInit(_db, cancellationToken).ConfigureAwait(false); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     #region 数据查询 | ||||
|   | ||||
| @@ -11,11 +11,10 @@ | ||||
| using System.Diagnostics; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
| using ThingsGateway.Plugin.DB; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
| namespace ThingsGateway.Plugin.DB; | ||||
|  | ||||
| /// <summary> | ||||
| /// SqlHistoryAlarm | ||||
| @@ -23,8 +22,61 @@ namespace ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
| public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm | ||||
| { | ||||
| #if !Management | ||||
|  | ||||
|     protected override void PluginChange(PluginEventData value) | ||||
|     { | ||||
|         if (_driverPropertys.PluginEventEnable == false) | ||||
|             return; | ||||
|         AddQueuePluginDataModel(new CacheDBItem<PluginEventData>(value)); | ||||
|     } | ||||
|  | ||||
|     protected override ValueTask<OperResult> UpdatePluginEventDataModel(List<CacheDBItem<PluginEventData>> item, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return UpdatePluginEventDataModel(item.Select(a => a.Value), cancellationToken); | ||||
|     } | ||||
|     private async ValueTask<OperResult> UpdatePluginEventDataModel(IEnumerable<PluginEventData> item, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var result = await InserableAsync(item.ToList(), cancellationToken).ConfigureAwait(false); | ||||
|         if (success != result.IsSuccess) | ||||
|         { | ||||
|             if (!result.IsSuccess) | ||||
|                 LogMessage?.LogWarning(result.ToString()); | ||||
|             success = result.IsSuccess; | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|     private async ValueTask<OperResult> InserableAsync(List<PluginEventData> dbInserts, CancellationToken cancellationToken) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             _db.Ado.CancellationToken = cancellationToken; | ||||
|             if (!_driverPropertys.BigTextScriptPluginEventDataHistoryTable.IsNullOrEmpty()) | ||||
|             { | ||||
|                 var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptPluginEventDataHistoryTable); | ||||
|  | ||||
|                 getDeviceModel.Logger = LogMessage; | ||||
|  | ||||
|                 await getDeviceModel.DBInsertable(_db, dbInserts, cancellationToken).ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return new OperResult("Script must be configured"); | ||||
|             } | ||||
|  | ||||
|             return OperResult.Success; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     protected override void AlarmChange(AlarmVariable alarmVariable) | ||||
|     { | ||||
|         if (_driverPropertys.VariableAlarmEnable == false) | ||||
|             return; | ||||
|         AddQueueAlarmModel(new CacheDBItem<AlarmVariable>(alarmVariable)); | ||||
|     } | ||||
|  | ||||
| @@ -87,5 +139,7 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| using ThingsGateway.Plugin.SqlDB; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
| namespace ThingsGateway.Plugin.DB; | ||||
|  | ||||
| /// <summary> | ||||
| /// <inheritdoc/> | ||||
| @@ -26,10 +26,17 @@ public class SqlHistoryAlarmProperty : BusinessPropertyWithCache | ||||
|     [DynamicProperty] | ||||
|     [Required] | ||||
|     public string TableName { get; set; } = "historyAlarm"; | ||||
|  | ||||
|     [DynamicProperty] | ||||
|     public bool VariableAlarmEnable { get; set; } = true; | ||||
|  | ||||
|     [DynamicProperty] | ||||
|     public bool PluginEventEnable { get; set; } = false; | ||||
|  | ||||
|     [DynamicProperty] | ||||
|     [Required] | ||||
|     [AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 1)] | ||||
|     public string BigTextConnectStr { get; set; } = "server=.;uid=sa;pwd=111111;database=test;"; | ||||
|     public string BigTextConnectStr { get; set; } = "server=.;uid=sa;pwd=111111;database=test;Encrypt=True;TrustServerCertificate=True;"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 历史表脚本 | ||||
| @@ -38,5 +45,15 @@ public class SqlHistoryAlarmProperty : BusinessPropertyWithCache | ||||
|     [AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)] | ||||
|     public string? BigTextScriptHistoryTable { get; set; } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 历史表脚本 | ||||
|     /// </summary> | ||||
|     [DynamicProperty] | ||||
|     [AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)] | ||||
|     public string? BigTextScriptPluginEventDataHistoryTable { get; set; } | ||||
|  | ||||
|     public override bool OnlineFilter { get; set; } = false; | ||||
| } | ||||
|   | ||||
| @@ -8,11 +8,11 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlHistoryAlarm; | ||||
| namespace ThingsGateway.Plugin.DB; | ||||
|  | ||||
| /// <summary> | ||||
| /// <inheritdoc/> | ||||
| /// </summary> | ||||
| public class SqlHistoryAlarmVariableProperty : VariablePropertyBase | ||||
| public class SqlHistoryAlarmVariableProperty : BusinessVariableProperty | ||||
| { | ||||
| } | ||||
| @@ -10,6 +10,6 @@ | ||||
|  | ||||
| namespace ThingsGateway.Plugin.TDengineDB; | ||||
|  | ||||
| public class TDengineDBProducerVariableProperty : VariablePropertyBase | ||||
| public class TDengineDBProducerVariableProperty : BusinessVariableProperty | ||||
| { | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user