Compare commits
	
		
			21 Commits
		
	
	
		
			10.10.1.0
			...
			10.10.15.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6660ce3e34 | ||
|   | 7499162c1a | ||
|   | 40208a5cd6 | ||
|   | fa347f4f68 | ||
|   | d7df6fc605 | ||
|   | eb4bb2fd48 | ||
|   | faa9858974 | ||
|   | 1b3d2dda49 | ||
|   | a8a9453611 | ||
|   | e84f42ce14 | ||
|   | 6f814cf6b8 | ||
|   | e36432e4e9 | ||
|   | ebd71e807b | ||
|   | 34000d8d7d | ||
|   | e785f6660c | ||
|   | 831c611797 | ||
|   | 453817ef86 | ||
|   | 8ce0b981c1 | ||
|   | 4e5c51b54c | ||
|   | 3cc9d31f28 | ||
|   | 10391f869b | 
| @@ -38,6 +38,7 @@ public class VerificatInfo : PrimaryIdEntity | |||||||
|     [AutoGenerateColumn(Filterable = true, Sortable = true)] |     [AutoGenerateColumn(Filterable = true, Sortable = true)] | ||||||
|     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] |     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] | ||||||
|     [IgnoreExcel] |     [IgnoreExcel] | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public override long Id { get; set; } |     public override long Id { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Admin.Application; | ||||||
| 
 | 
 | ||||||
| public class USheetDatas | public class USheetDatas | ||||||
| { | { | ||||||
| @@ -18,6 +18,7 @@ public class SessionOutput : PrimaryIdEntity | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 主键Id |     /// 主键Id | ||||||
|     /// </summary> |     /// </summary> | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public override long Id { get; set; } |     public override long Id { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -0,0 +1,57 @@ | |||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://thingsgateway.cn/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Admin.Application; | ||||||
|  |  | ||||||
|  | public static class USheetDataHelpers | ||||||
|  | { | ||||||
|  |     public static USheetDatas GetUSheetDatas(Dictionary<string, object> data) | ||||||
|  |     { | ||||||
|  |         var uSheetDatas = new USheetDatas(); | ||||||
|  |  | ||||||
|  |         foreach (var a in data) | ||||||
|  |         { | ||||||
|  |             var value = (a.Value as IEnumerable<Dictionary<string, object>>).ToList(); | ||||||
|  |  | ||||||
|  |             var uSheetData = new USheetData(); | ||||||
|  |             uSheetData.id = a.Key; | ||||||
|  |             uSheetData.name = a.Key; | ||||||
|  |  | ||||||
|  |             for (int row1 = 0; row1 < value.Count; row1++) | ||||||
|  |             { | ||||||
|  |                 if (row1 == 0) | ||||||
|  |                 { | ||||||
|  |                     Dictionary<int, USheetCelldata> usheetColldata = new(); | ||||||
|  |                     int col = 0; | ||||||
|  |                     foreach (var colData in value[row1]) | ||||||
|  |                     { | ||||||
|  |                         usheetColldata.Add(col, new USheetCelldata() { v = colData.Key }); | ||||||
|  |                         col++; | ||||||
|  |                     } | ||||||
|  |                     uSheetData.cellData.Add(row1, usheetColldata); | ||||||
|  |                 } | ||||||
|  |                 { | ||||||
|  |                     Dictionary<int, USheetCelldata> usheetColldata = new(); | ||||||
|  |                     int col = 0; | ||||||
|  |                     foreach (var colData in value[row1]) | ||||||
|  |                     { | ||||||
|  |                         usheetColldata.Add(col, new USheetCelldata() { v = colData.Value }); | ||||||
|  |                         col++; | ||||||
|  |                     } | ||||||
|  |                     uSheetData.cellData.Add(row1 + 1, usheetColldata); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             uSheetData.rowCount = uSheetData.cellData.Count + 100; | ||||||
|  |             uSheetData.columnCount = uSheetData.cellData.FirstOrDefault().Value?.Count ?? 0; | ||||||
|  |             uSheetDatas.sheets.Add(a.Key, uSheetData); | ||||||
|  |         } | ||||||
|  |         return uSheetDatas; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,7 +1,6 @@ | |||||||
| @namespace ThingsGateway.Gateway.Razor | @namespace ThingsGateway.Admin.Razor | ||||||
| @using ThingsGateway.Admin.Application | @using ThingsGateway.Admin.Application | ||||||
| @using ThingsGateway.Admin.Razor | @using ThingsGateway.Admin.Razor | ||||||
| @using ThingsGateway.Gateway.Application |  | ||||||
| 
 | 
 | ||||||
| <div class="h-600px"> | <div class="h-600px"> | ||||||
|     <UniverSheet @ref="_sheetExcel" OnReadyAsync="OnReadyAsync"></UniverSheet> |     <UniverSheet @ref="_sheetExcel" OnReadyAsync="OnReadyAsync"></UniverSheet> | ||||||
| @@ -8,9 +8,10 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
|  | using ThingsGateway.Admin.Application; | ||||||
| using ThingsGateway.NewLife.Json.Extension; | using ThingsGateway.NewLife.Json.Extension; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Razor; | namespace ThingsGateway.Admin.Razor; | ||||||
| 
 | 
 | ||||||
| public partial class USheet | public partial class USheet | ||||||
| { | { | ||||||
| @@ -6,6 +6,7 @@ | |||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | 		<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" /> | 		<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" /> | ||||||
|  | 		<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup Condition="'$(TargetFramework)'=='net8.0'"> | 	<ItemGroup Condition="'$(TargetFramework)'=='net8.0'"> | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ | |||||||
|     <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" /> |     <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" /> | ||||||
|  |  | ||||||
|     <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script> |     <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script> | ||||||
|     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js?v={this.GetType().Assembly.GetName().Version}")></script> |     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script> | ||||||
|     <script src="_framework/blazor.web.js"></script> |     <script src="_framework/blazor.web.js"></script> | ||||||
|     <!-- PWA Service Worker --> |     <!-- PWA Service Worker --> | ||||||
|     <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script> |     <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script> | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ | |||||||
|     </app> |     </app> | ||||||
|      |      | ||||||
|     <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script> |     <script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script> | ||||||
|     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script> |     <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script> | ||||||
|     <script src="_framework/blazor.server.js"></script> |     <script src="_framework/blazor.server.js"></script> | ||||||
|  |  | ||||||
|        <!-- PWA Service Worker --> |        <!-- PWA Service Worker --> | ||||||
|   | |||||||
| @@ -45,11 +45,11 @@ public class Startup : AppStartup | |||||||
|             options.ServicesStopConcurrently = true; |             options.ServicesStopConcurrently = true; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         //// 事件总线 |         // 事件总线 | ||||||
|         //services.AddEventBus(options => |         services.AddEventBus(options => | ||||||
|         //{ |         { | ||||||
|  |  | ||||||
|         //}); |         }); | ||||||
|  |  | ||||||
|         // 任务调度 |         // 任务调度 | ||||||
|         services.AddSchedule(options => options.AddPersistence<JobPersistence>()); |         services.AddSchedule(options => options.AddPersistence<JobPersistence>()); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Common; | ||||||
| 
 | 
 | ||||||
| public class SmartTriggerScheduler | public class SmartTriggerScheduler | ||||||
| { | { | ||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Common; | ||||||
| 
 | 
 | ||||||
| public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string> | public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string> | ||||||
| { | { | ||||||
| @@ -27,11 +27,11 @@ public class WebsiteOptions : IConfigurableOptions | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public bool Demo { get; set; } |     public bool Demo { get; set; } | ||||||
|  |  | ||||||
|     public bool WebPageEnable { get; set; } = true; |  | ||||||
|  |  | ||||||
|     public int MaxBlazorConnections { get; set; } = 5; |     public int MaxBlazorConnections { get; set; } = 5; | ||||||
|     public bool BlazorConnectionLimitEnable { get; set; } = false; |     public bool BlazorConnectionLimitEnable { get; set; } = false; | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 是否显示关于页面 |     /// 是否显示关于页面 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ | |||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" /> | 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" /> | ||||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.9.0" /> | 		<PackageReference Include="BootstrapBlazor" Version="9.9.1" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity | |||||||
|     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] |     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] | ||||||
|     [IgnoreExcel] |     [IgnoreExcel] | ||||||
|     [AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] |     [AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public virtual long Id { get; set; } |     public virtual long Id { get; set; } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -123,6 +123,15 @@ public static class QueryPageOptionsExtensions | |||||||
|         }; |         }; | ||||||
|         var items = datas.GetData(option, out var totalCount, where); |         var items = datas.GetData(option, out var totalCount, where); | ||||||
|         ret.TotalCount = totalCount; |         ret.TotalCount = totalCount; | ||||||
|  |  | ||||||
|  |         if (totalCount > 0) | ||||||
|  |         { | ||||||
|  |             if (!items.Any() && option.PageIndex != 1) | ||||||
|  |             { | ||||||
|  |                 option.PageIndex = 1; | ||||||
|  |                 items = datas.GetData(option, out totalCount, where); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         ret.Items = items.ToList(); |         ret.Items = items.ToList(); | ||||||
|         return ret; |         return ret; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new( | |||||||
|     public async Task<bool> DeleteAsync(IEnumerable<T> models) |     public async Task<bool> DeleteAsync(IEnumerable<T> models) | ||||||
|     { |     { | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         return await db.Deleteable<T>().In(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false); |         return await db.Deleteable<T>(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
| @@ -140,18 +140,22 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new( | |||||||
|             return (await db.UpdateableT(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0; |             return (await db.UpdateableT(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public virtual async Task<bool> SaveAsync(List<T> model, ItemChangedType changedType) |     public virtual async Task<bool> SaveAsync(List<T> model, ItemChangedType changedType) | ||||||
|  |     { | ||||||
|  |         return (await SaveReturnCountAsync(model, changedType).ConfigureAwait(false)) > 0; | ||||||
|  |     } | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public async Task<int> SaveReturnCountAsync(List<T> model, ItemChangedType changedType) | ||||||
|     { |     { | ||||||
|         using var db = GetDB(); |         using var db = GetDB(); | ||||||
|         if (changedType == ItemChangedType.Add) |         if (changedType == ItemChangedType.Add) | ||||||
|         { |         { | ||||||
|             return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0; |             return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false)); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0; |             return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -27,18 +27,27 @@ using System.Security.Claims; | |||||||
| using ThingsGateway.ConfigurableOptions; | using ThingsGateway.ConfigurableOptions; | ||||||
| using ThingsGateway.NewLife.Caching; | using ThingsGateway.NewLife.Caching; | ||||||
| using ThingsGateway.NewLife.Collections; | using ThingsGateway.NewLife.Collections; | ||||||
|  | using ThingsGateway.NewLife.Extension; | ||||||
| using ThingsGateway.NewLife.Log; | using ThingsGateway.NewLife.Log; | ||||||
| using ThingsGateway.Reflection; | using ThingsGateway.Reflection; | ||||||
| using ThingsGateway.Templates; | using ThingsGateway.Templates; | ||||||
|  |  | ||||||
| namespace ThingsGateway; | namespace ThingsGateway; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | public static class WebEnableVariable | ||||||
|  | { | ||||||
|  |     public static bool WebEnable => Environment.GetEnvironmentVariable(nameof(WebEnable)).ToBoolean(true); | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 全局应用类 | /// 全局应用类 | ||||||
| /// </summary> | /// </summary> | ||||||
| [SuppressSniffer] | [SuppressSniffer] | ||||||
| public static class App | public static class App | ||||||
| { | { | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 私有设置,避免重复解析 |     /// 私有设置,避免重复解析 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -157,7 +166,7 @@ public static class App | |||||||
|     var httpContextAccessor = RootServices?.GetService<IHttpContextAccessor>(); |     var httpContextAccessor = RootServices?.GetService<IHttpContextAccessor>(); | ||||||
|     try |     try | ||||||
|     { |     { | ||||||
|         return httpContextAccessor.HttpContext; |         return httpContextAccessor?.HttpContext; | ||||||
|     } |     } | ||||||
|     catch |     catch | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -213,12 +213,18 @@ public static class AppServiceCollectionExtensions | |||||||
|         // 缓存 |         // 缓存 | ||||||
|         if (cacheOptions.CacheType == CacheType.Memory) |         if (cacheOptions.CacheType == CacheType.Memory) | ||||||
|         { |         { | ||||||
|             services.AddSingleton<ICache, MemoryCache>(a => new() |             services.AddSingleton<ICache>(a => | ||||||
|             { |             { | ||||||
|                 Capacity = cacheOptions.MemoryCacheOptions.Capacity, |                 Cache.Default = new MemoryCache() | ||||||
|                 Expire = cacheOptions.MemoryCacheOptions.Expire, |                 { | ||||||
|                 Period = cacheOptions.MemoryCacheOptions.Period |                     Capacity = cacheOptions.MemoryCacheOptions.Capacity, | ||||||
|             }); |                     Expire = cacheOptions.MemoryCacheOptions.Expire, | ||||||
|  |                     Period = cacheOptions.MemoryCacheOptions.Period | ||||||
|  |                 }; | ||||||
|  |                 return Cache.Default; | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         } |         } | ||||||
|         else if (cacheOptions.CacheType == CacheType.Redis) |         else if (cacheOptions.CacheType == CacheType.Redis) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -85,11 +85,14 @@ internal static class InternalApp | |||||||
|             // 存储根服务(解决 Web 主机还未启动时在 HostedService 中使用 App.GetService 问题 |             // 存储根服务(解决 Web 主机还未启动时在 HostedService 中使用 App.GetService 问题 | ||||||
|             services.AddHostedService<GenericHostLifetimeEventsHostedService>(); |             services.AddHostedService<GenericHostLifetimeEventsHostedService>(); | ||||||
|  |  | ||||||
|             // 注册 Startup 过滤器 |             if (WebEnableVariable.WebEnable == true) | ||||||
|             services.AddTransient<IStartupFilter, StartupFilter>(); |             { | ||||||
|  |                 // 注册 Startup 过滤器 | ||||||
|  |                 services.AddTransient<IStartupFilter, StartupFilter>(); | ||||||
|  |  | ||||||
|             // 注册 HttpContextAccessor 服务 |                 // 注册 HttpContextAccessor 服务 | ||||||
|             services.AddHttpContextAccessor(); |                 services.AddHttpContextAccessor(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // 初始化应用服务 |             // 初始化应用服务 | ||||||
|             services.AddApp(); |             services.AddApp(); | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ namespace ThingsGateway; | |||||||
| /// </summary> | /// </summary> | ||||||
| public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions> | public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions> | ||||||
| { | { | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 是否启用规范化文档 |     /// 是否启用规范化文档 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
							
								
								
									
										341
									
								
								src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,341 @@ | |||||||
|  | // ------------------------------------------------------------------------ | ||||||
|  | // 版权信息 | ||||||
|  | // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||||
|  | // 所有权利保留。 | ||||||
|  | // 官方网站:https://baiqian.com | ||||||
|  | // | ||||||
|  | // 许可证信息 | ||||||
|  | // 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。 | ||||||
|  | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
|  | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using Microsoft.AspNetCore.Builder; | ||||||
|  | using Microsoft.AspNetCore.Hosting; | ||||||
|  | using Microsoft.Extensions.Configuration; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Microsoft.Extensions.Hosting; | ||||||
|  |  | ||||||
|  | using ThingsGateway; | ||||||
|  |  | ||||||
|  | namespace System; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// <see cref="WebApplication"/> 方式配置选项 | ||||||
|  | /// </summary> | ||||||
|  | [SuppressSniffer] | ||||||
|  | public sealed class MiniRunOptions : IRunOptions | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 内部构造函数 | ||||||
|  |     /// </summary> | ||||||
|  |     internal MiniRunOptions() | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认配置 | ||||||
|  |     /// </summary> | ||||||
|  |     public static MiniRunOptions Default { get; } = new MiniRunOptions(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认配置(带启动参数) | ||||||
|  |     /// </summary> | ||||||
|  |     public static MiniRunOptions Main(string[] args) | ||||||
|  |     { | ||||||
|  |         return Default.WithArgs(args); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认配置(静默启动) | ||||||
|  |     /// </summary> | ||||||
|  |     public static MiniRunOptions DefaultSilence { get; } = new MiniRunOptions().Silence(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 默认配置(静默启动 + 启动参数) | ||||||
|  |     /// </summary> | ||||||
|  |     public static MiniRunOptions MainSilence(string[] args) | ||||||
|  |     { | ||||||
|  |         return DefaultSilence.WithArgs(args); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="WebApplicationOptions"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="options"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureOptions(WebApplicationOptions options) | ||||||
|  |     { | ||||||
|  |         Options = options; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="IWebHostBuilder"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureBuilder(Action<IWebHostBuilder> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionBuilder = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="IHostBuilder"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureFirstActionBuilder(Action<IHostBuilder> configureAction) | ||||||
|  |     { | ||||||
|  |         FirstActionBuilder = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="IServiceCollection"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureServices(Action<IServiceCollection> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionServices = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="InjectOptions"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction"></param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureInject(Action<IWebHostBuilder, InjectOptions> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionInject = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="WebApplication"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction">配置委托</param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions Configure(Action<IHost> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionConfigure = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 配置 <see cref="ConfigurationManager"/> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="configureAction">配置委托</param> | ||||||
|  |     /// <returns><see cref="MiniRunOptions"/></returns> | ||||||
|  |     public MiniRunOptions ConfigureConfiguration(Action<IHostEnvironment, IConfiguration> configureAction) | ||||||
|  |     { | ||||||
|  |         ActionConfigurationManager = configureAction; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用服务组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddComponent<TComponent>() | ||||||
|  |         where TComponent : class, IServiceComponent, new() | ||||||
|  |     { | ||||||
|  |         ServiceComponents.Add(typeof(TComponent), null); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用服务组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <typeparam name="TComponentOptions"></typeparam> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddComponent<TComponent, TComponentOptions>(TComponentOptions options) | ||||||
|  |         where TComponent : class, IServiceComponent, new() | ||||||
|  |     { | ||||||
|  |         ServiceComponents.Add(typeof(TComponent), options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用服务组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="componentType">组件类型</param> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddComponent(Type componentType, object options) | ||||||
|  |     { | ||||||
|  |         ServiceComponents.Add(componentType, options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用中间件组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions UseComponent<TComponent>() | ||||||
|  |         where TComponent : class, IApplicationComponent, new() | ||||||
|  |     { | ||||||
|  |         ApplicationComponents.Add(typeof(TComponent), null); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用中间件组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <typeparam name="TComponentOptions"></typeparam> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions UseComponent<TComponent, TComponentOptions>(TComponentOptions options) | ||||||
|  |         where TComponent : class, IApplicationComponent, new() | ||||||
|  |     { | ||||||
|  |         ApplicationComponents.Add(typeof(TComponent), options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加应用中间件组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="componentType">组件类型</param> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions UseComponent(Type componentType, object options) | ||||||
|  |     { | ||||||
|  |         ApplicationComponents.Add(componentType, options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加 IWebHostBuilder 组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddWebComponent<TComponent>() | ||||||
|  |         where TComponent : class, IWebComponent, new() | ||||||
|  |     { | ||||||
|  |         WebComponents.Add(typeof(TComponent), null); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加 IWebHostBuilder 组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="TComponent">组件类型</typeparam> | ||||||
|  |     /// <typeparam name="TComponentOptions"></typeparam> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddWebComponent<TComponent, TComponentOptions>(TComponentOptions options) | ||||||
|  |         where TComponent : class, IWebComponent, new() | ||||||
|  |     { | ||||||
|  |         WebComponents.Add(typeof(TComponent), options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加 IWebHostBuilder 组件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="componentType">组件类型</param> | ||||||
|  |     /// <param name="options">组件参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions AddWebComponent(Type componentType, object options) | ||||||
|  |     { | ||||||
|  |         WebComponents.Add(componentType, options); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 标识主机静默启动 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>不阻塞程序运行</remarks> | ||||||
|  |     /// <param name="silence">静默启动</param> | ||||||
|  |     /// <param name="logging">静默启动日志状态,默认 false</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions Silence(bool silence = true, bool logging = false) | ||||||
|  |     { | ||||||
|  |         IsSilence = silence; | ||||||
|  |         SilenceLogging = logging; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 设置进程启动参数 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="args">启动参数</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public MiniRunOptions WithArgs(string[] args) | ||||||
|  |     { | ||||||
|  |         Args = args; | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// <see cref="WebApplicationOptions"/> | ||||||
|  |     /// </summary> | ||||||
|  |     internal WebApplicationOptions Options { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IServiceCollection"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IServiceCollection> ActionServices { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IWebHostBuilder"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IHostBuilder> FirstActionBuilder { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IWebHostBuilder"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IWebHostBuilder> ActionBuilder { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="InjectOptions"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IWebHostBuilder, InjectOptions> ActionInject { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IHost"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IHost> ActionConfigure { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义 <see cref="IConfiguration"/> 委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<IHostEnvironment, IConfiguration> ActionConfigurationManager { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 应用服务组件 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Dictionary<Type, object> ServiceComponents { get; set; } = new(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// IWebHostBuilder 组件 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Dictionary<Type, object> WebComponents { get; set; } = new(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 应用中间件组件 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Dictionary<Type, object> ApplicationComponents { get; set; } = new(); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 静默启动 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>不阻塞程序运行</remarks> | ||||||
|  |     internal bool IsSilence { get; private set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 静默启动日志状态 | ||||||
|  |     /// </summary> | ||||||
|  |     internal bool SilenceLogging { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 命令行参数 | ||||||
|  |     /// </summary> | ||||||
|  |     internal string[] Args { get; set; } | ||||||
|  | } | ||||||
| @@ -602,6 +602,33 @@ public static class Serve | |||||||
|         return app; |         return app; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 启动 WebApplication 主机 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>未包含 Web 基础功能,需手动注册服务/中间件</remarks> | ||||||
|  |     /// <param name="options">配置选项</param> | ||||||
|  |     /// <param name="urls">默认 5000/5001 端口</param> | ||||||
|  |     /// <param name="cancellationToken"></param> | ||||||
|  |     /// <returns><see cref="IHost"/></returns> | ||||||
|  |     public static async Task<IHost> RunAsync(MiniRunOptions options, string urls = default, CancellationToken cancellationToken = default) | ||||||
|  |     { | ||||||
|  |         // 构建 WebApplication 对象 | ||||||
|  |         BuildMiniApplication(options, urls, out var app); | ||||||
|  |  | ||||||
|  |         // 是否静默启动 | ||||||
|  |         if (!options.IsSilence) | ||||||
|  |         { | ||||||
|  |             // 配置启动地址和端口 | ||||||
|  |             await app.RunAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             await app.StartAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构建 WebApplication 对象 |     /// 构建 WebApplication 对象 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -616,8 +643,8 @@ public static class Serve | |||||||
|  |  | ||||||
|         // 初始化 WebApplicationBuilder |         // 初始化 WebApplicationBuilder | ||||||
|         var builder = (options.Options == null |         var builder = (options.Options == null | ||||||
|             ? WebApplication.CreateBuilder(args) |            ? WebApplication.CreateBuilder(args) | ||||||
|             : WebApplication.CreateBuilder(options.Options)); |            : WebApplication.CreateBuilder(options.Options)); | ||||||
|  |  | ||||||
|         // 调用自定义配置服务 |         // 调用自定义配置服务 | ||||||
|         options?.FirstActionBuilder?.Invoke(builder); |         options?.FirstActionBuilder?.Invoke(builder); | ||||||
| @@ -799,6 +826,132 @@ public static class Serve | |||||||
|         App.AppStartups.Clear(); |         App.AppStartups.Clear(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 构建 IHost 对象 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="options">配置选项</param> | ||||||
|  |     /// <param name="urls">默认 5000/5001 端口</param> | ||||||
|  |     /// <param name="app"><see cref="IHost"/></param> | ||||||
|  |     public static void BuildMiniApplication(MiniRunOptions options, string urls, out IHost app) | ||||||
|  |     { | ||||||
|  |         // 获取命令行参数 | ||||||
|  |         var args = options.Args ?? Environment.GetCommandLineArgs().Skip(1).ToArray(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         var builder = Host.CreateDefaultBuilder(args); | ||||||
|  |  | ||||||
|  |         // 静默启动排除指定日志类名 | ||||||
|  |         if (options.IsSilence && !options.SilenceLogging) | ||||||
|  |         { | ||||||
|  |             builder = builder.ConfigureLogging(logging => | ||||||
|  |             { | ||||||
|  |                 logging.AddFilter((provider, category, logLevel) => !SilenceExcludesOfLogCategoryName.Any(u => category.StartsWith(u))); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // 配置 Web 主机 | ||||||
|  |         builder = builder.ConfigureWebHost(webHostBuilder => | ||||||
|  |         { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             // 调用自定义配置服务 | ||||||
|  |             options?.FirstActionBuilder?.Invoke(builder); | ||||||
|  |  | ||||||
|  |             // 注册 WebApplicationBuilder 组件 | ||||||
|  |             if (options.WebComponents.Count > 0) | ||||||
|  |             { | ||||||
|  |                 foreach (var (componentType, opt) in options.WebComponents) | ||||||
|  |                 { | ||||||
|  |                     webHostBuilder.AddWebComponent(componentType, opt); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             webHostBuilder.Configure((WebHostBuilderContext app, IApplicationBuilder applicationBuilder) => | ||||||
|  |             { | ||||||
|  |  | ||||||
|  |                 // 添加自定义配置 | ||||||
|  |                 options.ActionConfigurationManager?.Invoke(app.HostingEnvironment, app.Configuration); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             // 初始化框架 | ||||||
|  |             webHostBuilder.Inject(options.ActionInject); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             // 配置服务 | ||||||
|  |             if (options.ServiceComponents.Count > 0) | ||||||
|  |             { | ||||||
|  |                 webHostBuilder = webHostBuilder.ConfigureServices(services => | ||||||
|  |                 { | ||||||
|  |                     // 注册应用服务组件 | ||||||
|  |                     foreach (var (componentType, opt) in options.ServiceComponents) | ||||||
|  |                     { | ||||||
|  |                         services.AddComponent(componentType, opt); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 配置启动地址和端口 | ||||||
|  |             var startUrls = !string.IsNullOrWhiteSpace(urls) ? urls : webHostBuilder.GetSetting(nameof(urls)); | ||||||
|  |  | ||||||
|  |             // 自定义启动端口 | ||||||
|  |             if (!string.IsNullOrWhiteSpace(startUrls)) | ||||||
|  |             { | ||||||
|  |                 webHostBuilder = webHostBuilder.UseUrls(startUrls); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             // 调用自定义配置 | ||||||
|  |             options?.ActionBuilder?.Invoke(webHostBuilder); | ||||||
|  |  | ||||||
|  |             // 配置中间件 | ||||||
|  |             if (options.ApplicationComponents.Count > 0) | ||||||
|  |             { | ||||||
|  |                 webHostBuilder = webHostBuilder.Configure((context, app) => | ||||||
|  |                 { | ||||||
|  |                     // 注册应用中间件组件 | ||||||
|  |                     foreach (var (componentType, opt) in options.ApplicationComponents) | ||||||
|  |                     { | ||||||
|  |                         app.UseComponent(context.HostingEnvironment, componentType, opt); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         builder = builder.ConfigureServices(services => | ||||||
|  |         { | ||||||
|  |             // 调用自定义配置服务 | ||||||
|  |             options?.ActionServices?.Invoke(services); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // 构建主机 | ||||||
|  |         app = builder.Build(); | ||||||
|  |  | ||||||
|  |         InternalApp.RootServices ??= app.Services; | ||||||
|  |  | ||||||
|  |         var applicationPartManager = app.Services.GetService<ApplicationPartManager>(); | ||||||
|  |  | ||||||
|  |         applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name)); | ||||||
|  |         // 配置所有 Starup Configure | ||||||
|  |         UseStartups(app.Services); | ||||||
|  |         // 释放内存 | ||||||
|  |         App.AppStartups.Clear(); | ||||||
|  |         // 调用自定义配置 | ||||||
|  |         options?.ActionConfigure?.Invoke(app); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 构建 IHost 对象 |     /// 构建 IHost 对象 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
| @@ -127,7 +127,8 @@ public sealed class DatabaseLogger : ILogger, IDisposable | |||||||
|         // 设置日志消息模板 |         // 设置日志消息模板 | ||||||
|         logMsg.Message = _options.MessageFormat != null |         logMsg.Message = _options.MessageFormat != null | ||||||
|             ? _options.MessageFormat(logMsg) |             ? _options.MessageFormat(logMsg) | ||||||
|             : Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider); |             : string.Empty; | ||||||
|  |         //: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider); | ||||||
|  |  | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         if (logMsg.Message is null) |         if (logMsg.Message is null) | ||||||
|   | |||||||
| @@ -683,13 +683,20 @@ public class MachineInfo : IExtend | |||||||
|             if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty()) |             if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty()) | ||||||
|                 Memory = (UInt64)str.TrimEnd(" kB").ToLong(); |                 Memory = (UInt64)str.TrimEnd(" kB").ToLong(); | ||||||
|  |  | ||||||
|  |             ulong ma = 0; | ||||||
|             if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty()) |             if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty()) | ||||||
|                 AvailableMemory = (UInt64)str.TrimEnd(" kB").ToLong(); |             { | ||||||
|             else if (dic.TryGetValue("MemFree", out str) && !str.IsNullOrEmpty()) |                 ma = (UInt64)(str.TrimEnd(" kB").ToLong()); | ||||||
|                 AvailableMemory = |             } | ||||||
|                     (UInt64)(str.TrimEnd(" kB").ToLong() + |  | ||||||
|                     dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0 + |             //低于3.14内核的版本用 free+cache | ||||||
|                     dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0); |             var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||||
|  |             var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||||
|  |             var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||||
|  |  | ||||||
|  |             var free = mf + mc + bf; | ||||||
|  |  | ||||||
|  |             AvailableMemory = ma > free ? ma : free; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // A2/A4温度获取,Buildroot,CPU温度和主板温度 |         // A2/A4温度获取,Buildroot,CPU温度和主板温度 | ||||||
|   | |||||||
| @@ -306,8 +306,19 @@ public class TimerScheduler : ILogFeature | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             var func = timer.Method.As<Func<Object?, Task>>(target); | #if NET6_0_OR_GREATER | ||||||
|             await func!(timer.State).ConfigureAwait(false); |             if (timer.IsValueTask) | ||||||
|  |             { | ||||||
|  |                 var func = timer.Method.As<Func<Object?, ValueTask>>(target); | ||||||
|  |                 await func!(timer.State).ConfigureAwait(false); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  | #endif | ||||||
|  |             { | ||||||
|  |                 var func = timer.Method.As<Func<Object?, Task>>(target); | ||||||
|  |                 await func!(timer.State).ConfigureAwait(false); | ||||||
|  |             } | ||||||
|  |  | ||||||
|         } |         } | ||||||
|         catch (ThreadAbortException) { throw; } |         catch (ThreadAbortException) { throw; } | ||||||
|         catch (ThreadInterruptedException) { throw; } |         catch (ThreadInterruptedException) { throw; } | ||||||
|   | |||||||
| @@ -87,6 +87,8 @@ public class TimerX : ITimer, ITimerx, IDisposable | |||||||
|  |  | ||||||
|     private DateTime _AbsolutelyNext; |     private DateTime _AbsolutelyNext; | ||||||
|     private readonly Cron[]? _crons; |     private readonly Cron[]? _crons; | ||||||
|  |  | ||||||
|  |     internal bool IsValueTask { get; } | ||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|     //    #region 静态 |     //    #region 静态 | ||||||
| @@ -158,6 +160,29 @@ public class TimerX : ITimer, ITimerx, IDisposable | |||||||
|         Init(dueTime); |         Init(dueTime); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | #if NET6_0_OR_GREATER | ||||||
|  |  | ||||||
|  |     /// <summary>实例化一个不可重入的定时器</summary> | ||||||
|  |     /// <param name="callback">委托</param> | ||||||
|  |     /// <param name="state">用户数据</param> | ||||||
|  |     /// <param name="dueTime">多久之后开始。毫秒</param> | ||||||
|  |     /// <param name="period">间隔周期。毫秒</param> | ||||||
|  |     /// <param name="scheduler">调度器</param> | ||||||
|  |     public TimerX(Func<Object, ValueTask> callback, Object? state, Int32 dueTime, Int32 period, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler) | ||||||
|  |     { | ||||||
|  |         IsValueTask = true; | ||||||
|  |         if (callback == null) throw new ArgumentNullException(nameof(callback)); | ||||||
|  |         if (dueTime < 0) throw new ArgumentOutOfRangeException(nameof(dueTime)); | ||||||
|  |  | ||||||
|  |         IsAsyncTask = true; | ||||||
|  |         Async = true; | ||||||
|  |         Period = period; | ||||||
|  |  | ||||||
|  |         Init(dueTime); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | #endif | ||||||
|  |  | ||||||
|     /// <summary>实例化一个绝对定时器,指定时刻执行,跟当前时间和SetNext无关</summary> |     /// <summary>实例化一个绝对定时器,指定时刻执行,跟当前时间和SetNext无关</summary> | ||||||
|     /// <param name="callback">委托</param> |     /// <param name="callback">委托</param> | ||||||
|     /// <param name="state">用户数据</param> |     /// <param name="state">用户数据</param> | ||||||
| @@ -210,6 +235,37 @@ public class TimerX : ITimer, ITimerx, IDisposable | |||||||
|         Init(ms); |         Init(ms); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | #if NET6_0_OR_GREATER | ||||||
|  |  | ||||||
|  |     /// <summary>实例化一个绝对定时器,指定时刻执行,跟当前时间和SetNext无关</summary> | ||||||
|  |     /// <param name="callback">委托</param> | ||||||
|  |     /// <param name="state">用户数据</param> | ||||||
|  |     /// <param name="startTime">绝对开始时间</param> | ||||||
|  |     /// <param name="period">间隔周期。毫秒</param> | ||||||
|  |     /// <param name="scheduler">调度器</param> | ||||||
|  |     public TimerX(Func<Object, ValueTask> callback, Object? state, DateTime startTime, Int32 period, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler) | ||||||
|  |     { | ||||||
|  |         IsValueTask = true; | ||||||
|  |         if (callback == null) throw new ArgumentNullException(nameof(callback)); | ||||||
|  |         if (startTime <= DateTime.MinValue) throw new ArgumentOutOfRangeException(nameof(startTime)); | ||||||
|  |         if (period <= 0) throw new ArgumentOutOfRangeException(nameof(period)); | ||||||
|  |  | ||||||
|  |         IsAsyncTask = true; | ||||||
|  |         Async = true; | ||||||
|  |         Period = period; | ||||||
|  |         Absolutely = true; | ||||||
|  |  | ||||||
|  |         //var now = DateTime.Now; | ||||||
|  |         var now = Scheduler.GetNow(); | ||||||
|  |         var next = startTime; | ||||||
|  |         while (next < now) next = next.AddMilliseconds(period); | ||||||
|  |  | ||||||
|  |         var ms = (Int64)(next - now).TotalMilliseconds; | ||||||
|  |         _AbsolutelyNext = next; | ||||||
|  |         Init(ms); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | #endif | ||||||
|     /// <summary>实例化一个Cron定时器</summary> |     /// <summary>实例化一个Cron定时器</summary> | ||||||
|     /// <param name="callback">委托</param> |     /// <param name="callback">委托</param> | ||||||
|     /// <param name="state">用户数据</param> |     /// <param name="state">用户数据</param> | ||||||
| @@ -274,6 +330,42 @@ public class TimerX : ITimer, ITimerx, IDisposable | |||||||
|         //Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now)); |         //Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | #if NET6_0_OR_GREATER | ||||||
|  |     /// <summary>实例化一个Cron定时器</summary> | ||||||
|  |     /// <param name="callback">委托</param> | ||||||
|  |     /// <param name="state">用户数据</param> | ||||||
|  |     /// <param name="cronExpression">Cron表达式。支持多个表达式,分号分隔</param> | ||||||
|  |     /// <param name="scheduler">调度器</param> | ||||||
|  |     public TimerX(Func<Object, ValueTask> callback, Object? state, String cronExpression, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler) | ||||||
|  |     { | ||||||
|  |         IsValueTask = true; | ||||||
|  |         if (callback == null) throw new ArgumentNullException(nameof(callback)); | ||||||
|  |         if (cronExpression.IsNullOrEmpty()) throw new ArgumentNullException(nameof(cronExpression)); | ||||||
|  |  | ||||||
|  |         var list = new List<Cron>(); | ||||||
|  |         foreach (var item in cronExpression.Split(";")) | ||||||
|  |         { | ||||||
|  |             var cron = new Cron(); | ||||||
|  |             if (!cron.Parse(item)) throw new ArgumentException($"Invalid Cron expression[{item}]", nameof(cronExpression)); | ||||||
|  |  | ||||||
|  |             list.Add(cron); | ||||||
|  |         } | ||||||
|  |         _crons = list.ToArray(); | ||||||
|  |  | ||||||
|  |         IsAsyncTask = true; | ||||||
|  |         Async = true; | ||||||
|  |         Absolutely = true; | ||||||
|  |  | ||||||
|  |         //var now = DateTime.Now; | ||||||
|  |         var now = Scheduler.GetNow(); | ||||||
|  |         var next = _crons.Min(e => e.GetNext(now)); | ||||||
|  |         var ms = (Int64)(next - now).TotalMilliseconds; | ||||||
|  |         _AbsolutelyNext = next; | ||||||
|  |         Init(ms); | ||||||
|  |         //Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now)); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|     public bool Disposed { get; private set; } |     public bool Disposed { get; private set; } | ||||||
|     /// <summary>销毁定时器</summary> |     /// <summary>销毁定时器</summary> | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|   | |||||||
| @@ -8,8 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using ThingsGateway.Common.Extension; |  | ||||||
| using ThingsGateway.NewLife; | using ThingsGateway.NewLife; | ||||||
|  | using ThingsGateway.Razor.Extension; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Razor; | namespace ThingsGateway.Razor; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | @namespace ThingsGateway.Razor | ||||||
|  |  | ||||||
|  | @if (show) | ||||||
|  | { | ||||||
|  |     <Spinner class="ms-auto"></Spinner> | ||||||
|  | } | ||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Gateway.Razor; | namespace ThingsGateway.Razor; | ||||||
| 
 | 
 | ||||||
| public partial class SpinnerComponent | public partial class SpinnerComponent | ||||||
| { | { | ||||||
| @@ -10,7 +10,7 @@ | |||||||
| 
 | 
 | ||||||
| using Microsoft.JSInterop; | using Microsoft.JSInterop; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Common.Extension; | namespace ThingsGateway.Razor.Extension; | ||||||
| 
 | 
 | ||||||
| /// <summary> | /// <summary> | ||||||
| /// JSRuntime扩展方法 | /// JSRuntime扩展方法 | ||||||
| @@ -49,4 +49,28 @@ public static class JSRuntimeExtensions | |||||||
|         { |         { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public static async ValueTask<T> GetLocalStorage<T>(this IJSRuntime jsRuntime, string name) | ||||||
|  |     { | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             return await jsRuntime.InvokeAsync<T>("getLocalStorage", name).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |         catch | ||||||
|  |         { | ||||||
|  |             return default; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static async ValueTask SetLocalStorage<T>(this IJSRuntime jsRuntime, string name, T data) | ||||||
|  |     { | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             await jsRuntime.InvokeVoidAsync("setLocalStorage", name, data).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |         catch | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -1,9 +0,0 @@ | |||||||
| // 设置 culture |  | ||||||
| function setCultureLocalStorage(culture) { |  | ||||||
|     localStorage.setItem("culture", culture); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 获取 culture |  | ||||||
| function getCultureLocalStorage() { |  | ||||||
|     return localStorage.getItem("culture"); |  | ||||||
| } |  | ||||||
							
								
								
									
										18
									
								
								src/Admin/ThingsGateway.Razor/wwwroot/js/localStorageUtil.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Admin/ThingsGateway.Razor/wwwroot/js/localStorageUtil.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | // 设置 culture | ||||||
|  | function setCultureLocalStorage(culture) { | ||||||
|  |     localStorage.setItem("culture", culture); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 获取 culture | ||||||
|  | function getCultureLocalStorage() { | ||||||
|  |     return localStorage.getItem("culture"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  function getLocalStorage(name) { | ||||||
|  |     return JSON.parse(localStorage.getItem(name)) ?? 0; | ||||||
|  | } | ||||||
|  |  function setLocalStorage(name, data) { | ||||||
|  |     if (localStorage) { | ||||||
|  |         localStorage.setItem(name, JSON.stringify(data)); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -27,16 +27,17 @@ | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <typeparam name="T">数据类型</typeparam> |         /// <typeparam name="T">数据类型</typeparam> | ||||||
|         /// <param name="insertDatas">要插入的数据列表</param> |         /// <param name="insertDatas">要插入的数据列表</param> | ||||||
|  |         /// <param name="tableName">表名称</param> | ||||||
|         /// <param name="dateFormat">日期格式字符串</param> |         /// <param name="dateFormat">日期格式字符串</param> | ||||||
|         /// <returns>插入的记录数</returns> |         /// <returns>插入的记录数</returns> | ||||||
|         public int BulkCopy<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() |         public int BulkCopy<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() | ||||||
|         { |         { | ||||||
|             int result = 0; |             int result = 0; | ||||||
|             // 使用分页方式处理大数据量插入 |             // 使用分页方式处理大数据量插入 | ||||||
|             db.Utilities.PageEach(insertDatas, pageSize, pageItems => |             db.Utilities.PageEach(insertDatas, pageSize, pageItems => | ||||||
|             { |             { | ||||||
|                 // 同步调用批量插入API并累加结果 |                 // 同步调用批量插入API并累加结果 | ||||||
|                 result += questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).GetAwaiter().GetResult(); |                 result += questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).GetAwaiter().GetResult(); | ||||||
|             }); |             }); | ||||||
|             return result; |             return result; | ||||||
|         } |         } | ||||||
| @@ -46,16 +47,17 @@ | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <typeparam name="T">数据类型</typeparam> |         /// <typeparam name="T">数据类型</typeparam> | ||||||
|         /// <param name="insertDatas">要插入的数据列表</param> |         /// <param name="insertDatas">要插入的数据列表</param> | ||||||
|  |         /// <param name="tableName">表名称</param> | ||||||
|         /// <param name="dateFormat">日期格式字符串</param> |         /// <param name="dateFormat">日期格式字符串</param> | ||||||
|         /// <returns>插入的记录数</returns> |         /// <returns>插入的记录数</returns> | ||||||
|         public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() |         public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() | ||||||
|         { |         { | ||||||
|             int result = 0; |             int result = 0; | ||||||
|             // 异步分页处理大数据量插入 |             // 异步分页处理大数据量插入 | ||||||
|             await db.Utilities.PageEachAsync(insertDatas, pageSize, async pageItems => |             await db.Utilities.PageEachAsync(insertDatas, pageSize, async pageItems => | ||||||
|             { |             { | ||||||
|                 // 异步调用批量插入API并累加结果 |                 // 异步调用批量插入API并累加结果 | ||||||
|                 result += await questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).ConfigureAwait(false); |                 result += await questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).ConfigureAwait(false); | ||||||
|             }).ConfigureAwait(false); |             }).ConfigureAwait(false); | ||||||
|             return result; |             return result; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -7,16 +7,12 @@ namespace ThingsGateway.SqlSugar | |||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// 绑定RestAPI需要的信息 |         /// 绑定RestAPI需要的信息 | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string httpPort, ref string username, ref string password) |         public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string username, ref string password) | ||||||
|         { |         { | ||||||
|             if (builder.TryGetValue("Host", out object hostValue)) |             if (builder.TryGetValue("Host", out object hostValue)) | ||||||
|             { |             { | ||||||
|                 host = Convert.ToString(hostValue); |                 host = Convert.ToString(hostValue); | ||||||
|             } |             } | ||||||
|             if (builder.TryGetValue("HttpPort", out object httpPortValue)) |  | ||||||
|             { |  | ||||||
|                 httpPort = Convert.ToString(httpPortValue); |  | ||||||
|             } |  | ||||||
|             if (builder.TryGetValue("Username", out object usernameValue)) |             if (builder.TryGetValue("Username", out object usernameValue)) | ||||||
|             { |             { | ||||||
|                 username = Convert.ToString(usernameValue); |                 username = Convert.ToString(usernameValue); | ||||||
|   | |||||||
| @@ -28,16 +28,16 @@ namespace ThingsGateway.SqlSugar | |||||||
|         /// 初始化 QuestDbRestAPI 实例 |         /// 初始化 QuestDbRestAPI 实例 | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <param name="db">SqlSugar 数据库客户端</param> |         /// <param name="db">SqlSugar 数据库客户端</param> | ||||||
|         public QuestDbRestAPI(ISqlSugarClient db) |         /// <param name="httpPort">restApi端口</param> | ||||||
|  |         public QuestDbRestAPI(ISqlSugarClient db, int httpPort = 9000) | ||||||
|         { |         { | ||||||
|             var builder = new DbConnectionStringBuilder(); |             var builder = new DbConnectionStringBuilder(); | ||||||
|             builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString; |             builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString; | ||||||
|             this.db = db; |             this.db = db; | ||||||
|             string httpPort = String.Empty; |  | ||||||
|             string host = String.Empty; |             string host = String.Empty; | ||||||
|             string username = String.Empty; |             string username = String.Empty; | ||||||
|             string password = String.Empty; |             string password = String.Empty; | ||||||
|             QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref httpPort, ref username, ref password); |             QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref username, ref password); | ||||||
|             BindHost(host, httpPort, username, password); |             BindHost(host, httpPort, username, password); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -51,9 +51,14 @@ namespace ThingsGateway.SqlSugar | |||||||
|             // HTTP GET 请求执行SQL |             // HTTP GET 请求执行SQL | ||||||
|             var result = string.Empty; |             var result = string.Empty; | ||||||
|             var url = $"{this.url}/exec?query={HttpUtility.UrlEncode(sql)}"; |             var url = $"{this.url}/exec?query={HttpUtility.UrlEncode(sql)}"; | ||||||
|  |  | ||||||
|  |             var request = new HttpRequestMessage(HttpMethod.Get, url); | ||||||
|             if (!string.IsNullOrWhiteSpace(authorization)) |             if (!string.IsNullOrWhiteSpace(authorization)) | ||||||
|                 client.DefaultRequestHeaders.Add("Authorization", authorization); |             { | ||||||
|             var httpResponseMessage = await client.GetAsync(url).ConfigureAwait(false); |                 request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             using var httpResponseMessage = await client.SendAsync(request).ConfigureAwait(false); | ||||||
|             result = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); |             result = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); | ||||||
|             return result; |             return result; | ||||||
|         } |         } | ||||||
| @@ -68,34 +73,34 @@ namespace ThingsGateway.SqlSugar | |||||||
|             return ExecuteCommandAsync(sql).GetAwaiter().GetResult(); |             return ExecuteCommandAsync(sql).GetAwaiter().GetResult(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <summary> |         ///// <summary> | ||||||
|         /// 异步批量插入单条数据 |         ///// 异步批量插入单条数据 | ||||||
|         /// </summary> |         ///// </summary> | ||||||
|         /// <typeparam name="T">数据类型</typeparam> |         ///// <typeparam name="T">数据类型</typeparam> | ||||||
|         /// <param name="insertData">要插入的数据</param> |         ///// <param name="insertData">要插入的数据</param> | ||||||
|         /// <param name="dateFormat">日期格式字符串</param> |         ///// <param name="dateFormat">日期格式字符串</param> | ||||||
|         /// <returns>影响的行数</returns> |         ///// <returns>影响的行数</returns> | ||||||
|         public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() |         //public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() | ||||||
|         { |         //{ | ||||||
|             if (db.CurrentConnectionConfig.MoreSettings == null) |         //    if (db.CurrentConnectionConfig.MoreSettings == null) | ||||||
|                 db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings(); |         //        db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings(); | ||||||
|             db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true; |         //    db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true; | ||||||
|             var sql = db.InsertableT(insertData).ToSqlString(); |         //    var sql = db.InsertableT(insertData).ToSqlString(); | ||||||
|             var result = await ExecuteCommandAsync(sql).ConfigureAwait(false); |         //    var result = await ExecuteCommandAsync(sql).ConfigureAwait(false); | ||||||
|             return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0; |         //    return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0; | ||||||
|         } |         //} | ||||||
|  |  | ||||||
|         /// <summary> |         ///// <summary> | ||||||
|         /// 同步批量插入单条数据 |         ///// 同步批量插入单条数据 | ||||||
|         /// </summary> |         ///// </summary> | ||||||
|         /// <typeparam name="T">数据类型</typeparam> |         ///// <typeparam name="T">数据类型</typeparam> | ||||||
|         /// <param name="insertData">要插入的数据</param> |         ///// <param name="insertData">要插入的数据</param> | ||||||
|         /// <param name="dateFormat">日期格式字符串</param> |         ///// <param name="dateFormat">日期格式字符串</param> | ||||||
|         /// <returns>影响的行数</returns> |         ///// <returns>影响的行数</returns> | ||||||
|         public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() |         //public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() | ||||||
|         { |         //{ | ||||||
|             return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult(); |         //    return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult(); | ||||||
|         } |         //} | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// 创建分页批量插入器 |         /// 创建分页批量插入器 | ||||||
| @@ -115,9 +120,10 @@ namespace ThingsGateway.SqlSugar | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <typeparam name="T">数据类型</typeparam> |         /// <typeparam name="T">数据类型</typeparam> | ||||||
|         /// <param name="insertList">要插入的数据列表</param> |         /// <param name="insertList">要插入的数据列表</param> | ||||||
|  |         /// <param name="tableName">表名称</param> | ||||||
|         /// <param name="dateFormat">日期格式字符串</param> |         /// <param name="dateFormat">日期格式字符串</param> | ||||||
|         /// <returns>插入的记录数</returns> |         /// <returns>插入的记录数</returns> | ||||||
|         public async Task<int> BulkCopyAsync<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() |         public async Task<int> BulkCopyAsync<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() | ||||||
|         { |         { | ||||||
|             var result = 0; |             var result = 0; | ||||||
|             var fileName = $"{Guid.NewGuid()}.csv"; |             var fileName = $"{Guid.NewGuid()}.csv"; | ||||||
| @@ -127,12 +133,12 @@ namespace ThingsGateway.SqlSugar | |||||||
|                 // 准备多部分表单数据 |                 // 准备多部分表单数据 | ||||||
|                 var boundary = "---------------" + DateTime.Now.Ticks.ToString("x"); |                 var boundary = "---------------" + DateTime.Now.Ticks.ToString("x"); | ||||||
|                 var list = new List<Hashtable>(); |                 var list = new List<Hashtable>(); | ||||||
|                 var name = db.EntityMaintenance.GetEntityInfo<T>().DbTableName; |                 tableName ??= db.EntityMaintenance.GetEntityInfo<T>().DbTableName; | ||||||
|  |  | ||||||
|                 // 获取或创建列信息缓存 |                 // 获取或创建列信息缓存 | ||||||
|                 var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode(); |                 var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode(); | ||||||
|                 var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () => |                 var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () => | ||||||
|                  db.CopyNew().DbMaintenance.GetColumnInfosByTableName(name)); |                  db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName)); | ||||||
|  |  | ||||||
|                 // 构建schema信息 |                 // 构建schema信息 | ||||||
|                 columns.ForEach(d => |                 columns.ForEach(d => | ||||||
| @@ -170,8 +176,8 @@ namespace ThingsGateway.SqlSugar | |||||||
|                 // 准备HTTP请求内容 |                 // 准备HTTP请求内容 | ||||||
|                 using var httpContent = new MultipartFormDataContent(boundary); |                 using var httpContent = new MultipartFormDataContent(boundary); | ||||||
|                 using var fileStream = File.OpenRead(filePath); |                 using var fileStream = File.OpenRead(filePath); | ||||||
|                 if (!string.IsNullOrWhiteSpace(this.authorization)) |                 //if (!string.IsNullOrWhiteSpace(this.authorization)) | ||||||
|                     client.DefaultRequestHeaders.Add("Authorization", this.authorization); |                 //    client.DefaultRequestHeaders.Add("Authorization", this.authorization); | ||||||
|                 httpContent.Add(new StringContent(schema), "schema"); |                 httpContent.Add(new StringContent(schema), "schema"); | ||||||
|                 var streamContent = new StreamContent(fileStream); |                 var streamContent = new StreamContent(fileStream); | ||||||
|                 streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); |                 streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); | ||||||
| @@ -183,8 +189,8 @@ namespace ThingsGateway.SqlSugar | |||||||
|                     "multipart/form-data; boundary=" + boundary); |                     "multipart/form-data; boundary=" + boundary); | ||||||
|  |  | ||||||
|                 // 发送请求并处理响应 |                 // 发送请求并处理响应 | ||||||
|                 var httpResponseMessage = |                 using var httpResponseMessage = | ||||||
|                     await Post(client, name, httpContent).ConfigureAwait(false); |                       await Post(client, tableName, httpContent).ConfigureAwait(false); | ||||||
|                 var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); |                 var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); | ||||||
|                 var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync); |                 var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync); | ||||||
|  |  | ||||||
| @@ -266,11 +272,12 @@ namespace ThingsGateway.SqlSugar | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <typeparam name="T">数据类型</typeparam> |         /// <typeparam name="T">数据类型</typeparam> | ||||||
|         /// <param name="insertList">要插入的数据列表</param> |         /// <param name="insertList">要插入的数据列表</param> | ||||||
|  |         /// <param name="tableName">表名称</param> | ||||||
|         /// <param name="dateFormat">日期格式字符串</param> |         /// <param name="dateFormat">日期格式字符串</param> | ||||||
|         /// <returns>插入的记录数</returns> |         /// <returns>插入的记录数</returns> | ||||||
|         public int BulkCopy<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() |         public int BulkCopy<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new() | ||||||
|         { |         { | ||||||
|             return BulkCopyAsync(insertList, dateFormat).GetAwaiter().GetResult(); |             return BulkCopyAsync(insertList, tableName, dateFormat).GetAwaiter().GetResult(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
| @@ -280,7 +287,7 @@ namespace ThingsGateway.SqlSugar | |||||||
|         /// <param name="httpPort">HTTP端口</param> |         /// <param name="httpPort">HTTP端口</param> | ||||||
|         /// <param name="username">用户名</param> |         /// <param name="username">用户名</param> | ||||||
|         /// <param name="password">密码</param> |         /// <param name="password">密码</param> | ||||||
|         private void BindHost(string host, string httpPort, string username, string password) |         private void BindHost(string host, int httpPort, string username, string password) | ||||||
|         { |         { | ||||||
|             url = host; |             url = host; | ||||||
|             if (url.EndsWith('/')) |             if (url.EndsWith('/')) | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ | |||||||
| { | { | ||||||
|     public static class QuestDbSqlSugarClientExtensions |     public static class QuestDbSqlSugarClientExtensions | ||||||
|     { |     { | ||||||
|         public static QuestDbRestAPI RestApi(this ISqlSugarClient db) |         public static QuestDbRestAPI RestApi(this ISqlSugarClient db, int httpPort = 9000) | ||||||
|         { |         { | ||||||
|             return new QuestDbRestAPI(db); |             return new QuestDbRestAPI(db, httpPort); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -111,7 +111,7 @@ namespace ThingsGateway.SqlSugar | |||||||
|         /// <param name="context">SqlSugar提供者</param> |         /// <param name="context">SqlSugar提供者</param> | ||||||
|         /// <param name="dataRecord">数据记录器</param> |         /// <param name="dataRecord">数据记录器</param> | ||||||
|         /// <param name="fieldNames">字段名列表</param> |         /// <param name="fieldNames">字段名列表</param> | ||||||
|         public IDataReaderEntityBuilder(SqlSugarProvider context, IDataRecord dataRecord, List<string> fieldNames) |         public IDataReaderEntityBuilder(SqlSugarProvider context, IDataRecord dataRecord, IEnumerable<string> fieldNames) | ||||||
|         { |         { | ||||||
|             this.Context = context; |             this.Context = context; | ||||||
|             this.DataRecord = dataRecord; |             this.DataRecord = dataRecord; | ||||||
|   | |||||||
| @@ -679,7 +679,7 @@ namespace ThingsGateway.SqlSugar | |||||||
|             IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => |             IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => | ||||||
|             { |             { | ||||||
|                 var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr, |                 var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr, | ||||||
|                     columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T)); |                     columns.Select(it => it.Item1)).CreateBuilder(typeof(T)); | ||||||
|                 return cacheResult; |                 return cacheResult; | ||||||
|             }); |             }); | ||||||
|             using (dr) |             using (dr) | ||||||
| @@ -706,7 +706,7 @@ namespace ThingsGateway.SqlSugar | |||||||
|             IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => |             IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => | ||||||
|             { |             { | ||||||
|                 var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr, |                 var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr, | ||||||
|                     columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T)); |                     columns.Select(it => it.Item1)).CreateBuilder(typeof(T)); | ||||||
|                 return cacheResult; |                 return cacheResult; | ||||||
|             }); |             }); | ||||||
|             if (cancellationToken.IsCancellationRequested) yield break; |             if (cancellationToken.IsCancellationRequested) yield break; | ||||||
| @@ -743,7 +743,7 @@ namespace ThingsGateway.SqlSugar | |||||||
|             IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => |             IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => | ||||||
|             { |             { | ||||||
|                 var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr, |                 var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr, | ||||||
|                     columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T)); |                     columns.Select(it => it.Item1)).CreateBuilder(typeof(T)); | ||||||
|                 return cacheResult; |                 return cacheResult; | ||||||
|             }); |             }); | ||||||
|             using (dr) |             using (dr) | ||||||
| @@ -775,7 +775,7 @@ namespace ThingsGateway.SqlSugar | |||||||
|             IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => |             IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => | ||||||
|             { |             { | ||||||
|                 var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr, |                 var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr, | ||||||
|                     columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T)); |                     columns.Select(it => it.Item1)).CreateBuilder(typeof(T)); | ||||||
|                 return cacheResult; |                 return cacheResult; | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,6 @@ | |||||||
|         V Get<V>(string key); |         V Get<V>(string key); | ||||||
|         IEnumerable<string> GetAllKey<V>(); |         IEnumerable<string> GetAllKey<V>(); | ||||||
|         void Remove<V>(string key); |         void Remove<V>(string key); | ||||||
|         V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue); |         V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -31,7 +31,7 @@ namespace ThingsGateway.SqlSugar | |||||||
|             return ReflectionInoCore<V>.GetInstance().GetAllKey(); |             return ReflectionInoCore<V>.GetInstance().GetAllKey(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue) |         public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600) | ||||||
|         { |         { | ||||||
|             return ReflectionInoCore<V>.GetInstance().GetOrCreate(cacheKey, create); |             return ReflectionInoCore<V>.GetInstance().GetOrCreate(cacheKey, create); | ||||||
|         } |         } | ||||||
| @@ -43,10 +43,13 @@ namespace ThingsGateway.SqlSugar | |||||||
|     } |     } | ||||||
|     public class ReflectionInoCore<V> |     public class ReflectionInoCore<V> | ||||||
|     { |     { | ||||||
|         private MemoryCache InstanceCache => MemoryCache.Instance; |         private MemoryCache InstanceCache = new MemoryCache() { Expire = 180 }; | ||||||
|         private static ReflectionInoCore<V> _instance = null; |         private static ReflectionInoCore<V> _instance = null; | ||||||
|         private static readonly object _instanceLock = new object(); |         private static readonly object _instanceLock = new object(); | ||||||
|         private ReflectionInoCore() { } |         private ReflectionInoCore() | ||||||
|  |         { | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public V this[string key] |         public V this[string key] | ||||||
|         { |         { | ||||||
| @@ -86,7 +89,7 @@ namespace ThingsGateway.SqlSugar | |||||||
|  |  | ||||||
|         public void Add(string key, V value, int cacheDurationInSeconds) |         public void Add(string key, V value, int cacheDurationInSeconds) | ||||||
|         { |         { | ||||||
|             Check.ThrowNotSupportedException("ReflectionInoCache.Add(string key, V value, int cacheDurationInSeconds)"); |             this.InstanceCache.Add<V>(key, value, cacheDurationInSeconds); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void Remove(string key) |         public void Remove(string key) | ||||||
| @@ -107,9 +110,10 @@ namespace ThingsGateway.SqlSugar | |||||||
|             return this.InstanceCache.Keys; |             return this.InstanceCache.Keys; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public V GetOrCreate(string cacheKey, Func<V> create) |         public V GetOrCreate(string cacheKey, Func<V> create, int expire = 3600) | ||||||
|         { |         { | ||||||
|             return InstanceCache.GetOrAdd<V>(cacheKey, (a) => create()); |             return InstanceCache.GetOrAdd<V>(cacheKey, (a) => | ||||||
|  |             create(), expire); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     public static class ReflectionInoHelper |     public static class ReflectionInoHelper | ||||||
|   | |||||||
| @@ -447,6 +447,28 @@ namespace ThingsGateway.SqlSugar | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true) |         public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true) | ||||||
|  |         { | ||||||
|  |  | ||||||
|  |             if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>(); | ||||||
|  |             string cacheKey = "QuestDB.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId; | ||||||
|  |             cacheKey = GetCacheKey(cacheKey); | ||||||
|  |  | ||||||
|  |             if (isCache) | ||||||
|  |             { | ||||||
|  |  | ||||||
|  |                 return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => | ||||||
|  |                 { | ||||||
|  |                     return GetColInfo(tableName); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 return GetColInfo(tableName); | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private List<DbColumnInfo> GetColInfo(string tableName) | ||||||
|         { |         { | ||||||
|             var sql = String.Format(GetColumnInfosByTableNameSql, tableName); |             var sql = String.Format(GetColumnInfosByTableNameSql, tableName); | ||||||
|             List<DbColumnInfo> result = new List<DbColumnInfo>(); |             List<DbColumnInfo> result = new List<DbColumnInfo>(); | ||||||
|   | |||||||
| @@ -406,22 +406,24 @@ AND sql LIKE '%" + tableName + "%'"); | |||||||
|         public override bool CreateDatabase(string databaseName, string databaseDirectory = null) |         public override bool CreateDatabase(string databaseName, string databaseDirectory = null) | ||||||
|         { |         { | ||||||
|             var connString = this.Context.CurrentConnectionConfig.ConnectionString; |             var connString = this.Context.CurrentConnectionConfig.ConnectionString; | ||||||
|             var path = Regex.Match(connString, @"[a-z,A-Z]\:\\.+\\").Value; |  | ||||||
|             if (path.IsNullOrEmpty()) |  | ||||||
|  |             // 提取 Data Source=xxx(不管是绝对还是相对路径) | ||||||
|  |             var match = Regex.Match(connString, @"(?i)Data\s+Source\s*=\s*(.+?)(;|$)"); | ||||||
|  |             if (match.Success) | ||||||
|             { |             { | ||||||
|                 path = Regex.Match(connString, @"\/.+\/").Value; |                 var filePath = match.Groups[1].Value.Trim(); // => ./DB/data.sqlite | ||||||
|             } |                 var folderPath = Path.GetDirectoryName(filePath); // => ./DB | ||||||
|             if (path.IsNullOrEmpty()) |  | ||||||
|             { |                 if (!folderPath.IsNullOrEmpty()) | ||||||
|                 path = Regex.Match(connString, @"[a-z,A-Z]\:\\").Value; |  | ||||||
|             } |  | ||||||
|             if (!path.IsNullOrEmpty()) |  | ||||||
|             { |  | ||||||
|                 if (!FileHelper.IsExistDirectory(path)) |  | ||||||
|                 { |                 { | ||||||
|                     FileHelper.CreateDirectory(path); |                     if (!FileHelper.IsExistDirectory(folderPath)) | ||||||
|  |                     { | ||||||
|  |                         FileHelper.CreateDirectory(folderPath); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             this.Context.Ado.Connection.Open(); |             this.Context.Ado.Connection.Open(); | ||||||
|             this.Context.Ado.Connection.Close(); |             this.Context.Ado.Connection.Close(); | ||||||
|             return true; |             return true; | ||||||
|   | |||||||
| @@ -717,8 +717,32 @@ namespace ThingsGateway.SqlSugar | |||||||
|         /// <returns>列信息列表</returns> |         /// <returns>列信息列表</returns> | ||||||
|         public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true) |         public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true) | ||||||
|         { |         { | ||||||
|             var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 "; |  | ||||||
|  |             if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>(); | ||||||
|  |             string cacheKey = "TDengine.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId; | ||||||
|  |             cacheKey = GetCacheKey(cacheKey); | ||||||
|  |  | ||||||
|  |             if (isCache) | ||||||
|  |             { | ||||||
|  |  | ||||||
|  |                 return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () => | ||||||
|  |                      { | ||||||
|  |                          return GetColInfo(tableName); | ||||||
|  |                      }); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 return GetColInfo(tableName); | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private List<DbColumnInfo> GetColInfo(string tableName) | ||||||
|  |         { | ||||||
|             List<DbColumnInfo> result = new List<DbColumnInfo>(); |             List<DbColumnInfo> result = new List<DbColumnInfo>(); | ||||||
|  |  | ||||||
|  |             var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 "; | ||||||
|             DataTable dt = null; |             DataTable dt = null; | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|   | |||||||
| @@ -1,14 +1,15 @@ | |||||||
| <Project> | <Project> | ||||||
|  |  | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<PluginVersion>10.10.1</PluginVersion> | 		<PluginVersion>10.10.12</PluginVersion> | ||||||
| 		<ProPluginVersion>10.10.1</ProPluginVersion> | 		<ProPluginVersion>10.10.12</ProPluginVersion> | ||||||
| 		<DefaultVersion>10.10.1</DefaultVersion> | 		<DefaultVersion>10.10.15</DefaultVersion> | ||||||
| 		<AuthenticationVersion>2.9.29</AuthenticationVersion> | 		<AuthenticationVersion>10.10.1</AuthenticationVersion> | ||||||
| 		<SourceGeneratorVersion>10.9.29</SourceGeneratorVersion> | 		<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion> | ||||||
| 		<NET8Version>8.0.18</NET8Version> | 		<NET8Version>8.0.19</NET8Version> | ||||||
| 		<NET9Version>9.0.7</NET9Version> | 		<NET9Version>9.0.8</NET9Version> | ||||||
| 		<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages> | 		<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages> | ||||||
|  | 		<IsTrimmable>false</IsTrimmable> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
|  |  | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <Project> | <Project> | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks> | 		<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0</TargetFrameworks> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
|   | |||||||
| @@ -77,7 +77,8 @@ public partial class LogConsole : IDisposable | |||||||
|  |  | ||||||
|     [Inject] |     [Inject] | ||||||
|     private ToastService ToastService { get; set; } |     private ToastService ToastService { get; set; } | ||||||
|  |     [Inject] | ||||||
|  |     ITextFileReadService TextFileReadService { get; set; } | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|         Disposed = true; |         Disposed = true; | ||||||
| @@ -94,7 +95,7 @@ public partial class LogConsole : IDisposable | |||||||
|  |  | ||||||
|             if (LogPath != null) |             if (LogPath != null) | ||||||
|             { |             { | ||||||
|                 var files = TextFileReader.GetFiles(LogPath); |                 var files = await TextFileReadService.GetLogFilesAsync(LogPath); | ||||||
|                 if (!files.IsSuccess) |                 if (!files.IsSuccess) | ||||||
|                 { |                 { | ||||||
|                     Messages = new List<LogMessage>(); |                     Messages = new List<LogMessage>(); | ||||||
| @@ -105,7 +106,7 @@ public partial class LogConsole : IDisposable | |||||||
|                     await Task.Run(async () => |                     await Task.Run(async () => | ||||||
|                     { |                     { | ||||||
|                         Stopwatch sw = Stopwatch.StartNew(); |                         Stopwatch sw = Stopwatch.StartNew(); | ||||||
|                         var result = TextFileReader.LastLog(files.Content.FirstOrDefault()); |                         var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault()); | ||||||
|                         if (result.IsSuccess) |                         if (result.IsSuccess) | ||||||
|                         { |                         { | ||||||
|                             Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList(); |                             Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList(); | ||||||
| @@ -143,7 +144,7 @@ public partial class LogConsole : IDisposable | |||||||
|     { |     { | ||||||
|         if (LogPath != null) |         if (LogPath != null) | ||||||
|         { |         { | ||||||
|             var files = TextFileReader.GetFiles(LogPath); |             var files = await TextFileReadService.GetLogFilesAsync(LogPath); | ||||||
|             if (files.IsSuccess) |             if (files.IsSuccess) | ||||||
|             { |             { | ||||||
|                 foreach (var item in files.Content) |                 foreach (var item in files.Content) | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ public class PlatformService : IPlatformService | |||||||
|  |  | ||||||
|     public async Task OnLogExport(string logPath) |     public async Task OnLogExport(string logPath) | ||||||
|     { |     { | ||||||
|         var files = TextFileReader.GetFiles(logPath); |         var files = TextFileReader.GetLogFilesAsync(logPath); | ||||||
|         if (!files.IsSuccess) |         if (!files.IsSuccess) | ||||||
|         { |         { | ||||||
|             return; |             return; | ||||||
|   | |||||||
| @@ -33,5 +33,6 @@ public class Startup : AppStartup | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         services.AddScoped<IPlatformService, PlatformService>(); |         services.AddScoped<IPlatformService, PlatformService>(); | ||||||
|  |         services.AddSingleton<ITextFileReadService, TextFileReadService>(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel | |||||||
|     public OtherChannel(IChannelOptions channelOptions) |     public OtherChannel(IChannelOptions channelOptions) | ||||||
|     { |     { | ||||||
|         ChannelOptions = channelOptions; |         ChannelOptions = channelOptions; | ||||||
|  |         ResetSign(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; |     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; | ||||||
| @@ -39,7 +40,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel | |||||||
|         pool?.SafeDispose(); |         pool?.SafeDispose(); | ||||||
|     } |     } | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelReceivedEventHandler ChannelReceived { get; set; } = new(); |     public ChannelReceivedEventHandler ChannelReceived { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public IChannelOptions ChannelOptions { get; } |     public IChannelOptions ChannelOptions { get; } | ||||||
| @@ -51,16 +52,16 @@ public class OtherChannel : SetupConfigObject, IClientChannel | |||||||
|     public ConcurrentList<IDevice> Collects { get; } = new(); |     public ConcurrentList<IDevice> Collects { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Started { get; set; } = new(); |     public ChannelEventHandler Started { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Starting { get; set; } = new(); |     public ChannelEventHandler Starting { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoped { get; set; } = new(); |     public ChannelEventHandler Stoped { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoping { get; set; } = new(); |     public ChannelEventHandler Stoping { get; } = new(); | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 等待池 |     /// 等待池 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | |||||||
|     public SerialPortChannel(IChannelOptions channelOptions) |     public SerialPortChannel(IChannelOptions channelOptions) | ||||||
|     { |     { | ||||||
|         ChannelOptions = channelOptions; |         ChannelOptions = channelOptions; | ||||||
|  |         ResetSign(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; |     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; | ||||||
| @@ -36,7 +37,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | |||||||
|         pool?.SafeDispose(); |         pool?.SafeDispose(); | ||||||
|     } |     } | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelReceivedEventHandler ChannelReceived { get; set; } = new(); |     public ChannelReceivedEventHandler ChannelReceived { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public IChannelOptions ChannelOptions { get; } |     public IChannelOptions ChannelOptions { get; } | ||||||
| @@ -51,16 +52,16 @@ public class SerialPortChannel : SerialPortClient, IClientChannel | |||||||
|     public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter; |     public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Started { get; set; } = new(); |     public ChannelEventHandler Started { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Starting { get; set; } = new(); |     public ChannelEventHandler Starting { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoped { get; set; } = new(); |     public ChannelEventHandler Stoped { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoping { get; set; } = new(); |     public ChannelEventHandler Stoping { get; } = new(); | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 等待池 |     /// 等待池 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ public class TcpClientChannel : TcpClient, IClientChannel | |||||||
|     public TcpClientChannel(IChannelOptions channelOptions) |     public TcpClientChannel(IChannelOptions channelOptions) | ||||||
|     { |     { | ||||||
|         ChannelOptions = channelOptions; |         ChannelOptions = channelOptions; | ||||||
|  |         ResetSign(); | ||||||
|     } |     } | ||||||
|     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; |     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; | ||||||
|     public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue) |     public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue) | ||||||
|   | |||||||
| @@ -134,6 +134,7 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp | |||||||
|  |  | ||||||
|     protected override void SafetyDispose(bool disposing) |     protected override void SafetyDispose(bool disposing) | ||||||
|     { |     { | ||||||
|  |         m_transport?.SafeCancel(); | ||||||
|         m_transport?.SafeDispose(); |         m_transport?.SafeDispose(); | ||||||
|         base.SafetyDispose(disposing); |         base.SafetyDispose(disposing); | ||||||
|     } |     } | ||||||
| @@ -179,7 +180,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann | |||||||
|     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; |     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelReceivedEventHandler ChannelReceived { get; set; } = new(); |     public ChannelReceivedEventHandler ChannelReceived { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public IChannelOptions ChannelOptions { get; } |     public IChannelOptions ChannelOptions { get; } | ||||||
| @@ -191,15 +192,15 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann | |||||||
|     public bool Online => ServerState == ServerState.Running; |     public bool Online => ServerState == ServerState.Running; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Started { get; set; } = new(); |     public ChannelEventHandler Started { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Starting { get; set; } = new(); |     public ChannelEventHandler Starting { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoped { get; set; } = new(); |     public ChannelEventHandler Stoped { get; } = new(); | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoping { get; set; } = new(); |     public ChannelEventHandler Stoping { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public Task<Result> CloseAsync(string msg, CancellationToken token) |     public Task<Result> CloseAsync(string msg, CancellationToken token) | ||||||
| @@ -227,8 +228,8 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann | |||||||
|         data.ResetSign(MinSign, MaxSign); |         data.ResetSign(MinSign, MaxSign); | ||||||
|         return data; |         return data; | ||||||
|     } |     } | ||||||
|     public int MaxSign { get; set; } |     public int MaxSign { get; private set; } = 0; | ||||||
|     public int MinSign { get; set; } |     public int MinSign { get; private set; } = ushort.MaxValue; | ||||||
|     public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue) |     public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue) | ||||||
|     { |     { | ||||||
|         MinSign = minSign; |         MinSign = minSign; | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel | |||||||
|         pool?.SafeDispose(); |         pool?.SafeDispose(); | ||||||
|     } |     } | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelReceivedEventHandler ChannelReceived { get; set; } = new(); |     public ChannelReceivedEventHandler ChannelReceived { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public IChannelOptions ChannelOptions { get; internal set; } |     public IChannelOptions ChannelOptions { get; internal set; } | ||||||
| @@ -47,15 +47,15 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel | |||||||
|     public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter; |     public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Started { get; set; } = new(); |     public ChannelEventHandler Started { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Starting { get; set; } = new(); |     public ChannelEventHandler Starting { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoped { get; set; } = new(); |     public ChannelEventHandler Stoped { get; } = new(); | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoping { get; set; } = new(); |     public ChannelEventHandler Stoping { get; } = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 等待池 |     /// 等待池 | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel | |||||||
|     public UdpSessionChannel(IChannelOptions channelOptions) |     public UdpSessionChannel(IChannelOptions channelOptions) | ||||||
|     { |     { | ||||||
|         ChannelOptions = channelOptions; |         ChannelOptions = channelOptions; | ||||||
|  |         ResetSign(); | ||||||
|     } |     } | ||||||
|     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; |     public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config; | ||||||
|  |  | ||||||
| @@ -37,7 +38,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelReceivedEventHandler ChannelReceived { get; set; } = new(); |     public ChannelReceivedEventHandler ChannelReceived { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public IChannelOptions ChannelOptions { get; } |     public IChannelOptions ChannelOptions { get; } | ||||||
| @@ -55,15 +56,15 @@ public class UdpSessionChannel : UdpSession, IClientChannel | |||||||
|     public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter; |     public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Started { get; set; } = new(); |     public ChannelEventHandler Started { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Starting { get; set; } = new(); |     public ChannelEventHandler Starting { get; } = new(); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoped { get; set; } = new(); |     public ChannelEventHandler Stoped { get; } = new(); | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ChannelEventHandler Stoping { get; set; } = new(); |     public ChannelEventHandler Stoping { get; } = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 等待池 |     /// 等待池 | ||||||
| @@ -204,6 +205,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel | |||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override void SafetyDispose(bool disposing) |     protected override void SafetyDispose(bool disposing) | ||||||
|     { |     { | ||||||
|  |         m_transport?.SafeCancel(); | ||||||
|         m_transport?.SafeDispose(); |         m_transport?.SafeDispose(); | ||||||
|         WaitHandlePool.SafeDispose(); |         WaitHandlePool.SafeDispose(); | ||||||
|         base.SafetyDispose(disposing); |         base.SafetyDispose(disposing); | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ namespace ThingsGateway.Foundation; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// 协议基类 | /// 协议基类 | ||||||
| /// </summary> | /// </summary> | ||||||
| public abstract class DeviceBase : DisposableObject, IDevice | public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||||
| { | { | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public IChannel Channel { get; private set; } |     public IChannel Channel { get; private set; } | ||||||
| @@ -553,11 +553,11 @@ public abstract class DeviceBase : DisposableObject, IDevice | |||||||
|         WaitLock? waitLock = null; |         WaitLock? waitLock = null; | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|  |             await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|             var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null; |             var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null; | ||||||
|             waitLock = GetWaitLock(clientChannel, dtuId); |             waitLock = GetWaitLock(clientChannel, dtuId); | ||||||
|  |  | ||||||
|             await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|             await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); |             await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|             EndPoint? endPoint = GetUdpEndpoint(dtuId); |             EndPoint? endPoint = GetUdpEndpoint(dtuId); | ||||||
| @@ -585,13 +585,6 @@ public abstract class DeviceBase : DisposableObject, IDevice | |||||||
|             } |             } | ||||||
|             catch (Exception ex) |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 if (cancellationToken.IsCancellationRequested) |  | ||||||
|                 { |  | ||||||
|                     if (!this.DisposedValue) |  | ||||||
|                     { |  | ||||||
|                         await Task.Delay(timeout, Channel.ClosedToken).ConfigureAwait(false); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 return new MessageBase(ex); |                 return new MessageBase(ex); | ||||||
|             } |             } | ||||||
|             var result = waitData.Check(); |             var result = waitData.Check(); | ||||||
| @@ -1041,6 +1034,56 @@ public abstract class DeviceBase : DisposableObject, IDevice | |||||||
|         base.Dispose(disposing); |         base.Dispose(disposing); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     protected override async Task DisposeAsync(bool disposing) | ||||||
|  |     { | ||||||
|  |         if (Channel != null) | ||||||
|  |         { | ||||||
|  |             Channel.Starting.Remove(ChannelStarting); | ||||||
|  |             Channel.Stoped.Remove(ChannelStoped); | ||||||
|  |             Channel.Started.Remove(ChannelStarted); | ||||||
|  |             Channel.Stoping.Remove(ChannelStoping); | ||||||
|  |             Channel.ChannelReceived.Remove(ChannelReceived); | ||||||
|  |  | ||||||
|  |             if (Channel.Collects.Count == 1) | ||||||
|  |             { | ||||||
|  |                 if (Channel is ITcpServiceChannel tcpServiceChannel) | ||||||
|  |                 { | ||||||
|  |                     tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool.SafeDispose()); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     //只关闭,不释放 | ||||||
|  |                     await Channel.CloseAsync().ConfigureAwait(false); | ||||||
|  |                     if (Channel is IClientChannel client) | ||||||
|  |                     { | ||||||
|  |                         client.WaitHandlePool.SafeDispose(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     Logger?.LogWarning(ex); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 if (Channel is ITcpServiceChannel tcpServiceChannel && this is IDtu dtu) | ||||||
|  |                 { | ||||||
|  |                     if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client)) | ||||||
|  |                     { | ||||||
|  |                         client.WaitHandlePool?.SafeDispose(); | ||||||
|  |                         await client.CloseAsync().ConfigureAwait(false); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             Channel.Collects.Remove(this); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         _deviceLogger?.TryDispose(); | ||||||
|  |         base.Dispose(disposing); | ||||||
|  |     } | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public virtual Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config) |     public virtual Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config) | ||||||
|     { |     { | ||||||
| @@ -1058,4 +1101,5 @@ public abstract class DeviceBase : DisposableObject, IDevice | |||||||
|         return a => { }; |         return a => { }; | ||||||
|     } |     } | ||||||
|     public abstract ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default); |     public abstract ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ namespace ThingsGateway.Foundation; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// 协议设备接口 | /// 协议设备接口 | ||||||
| /// </summary> | /// </summary> | ||||||
| public interface IDevice : IDisposable, IDisposableObject | public interface IDevice : IDisposable, IDisposableObject, IAsyncDisposable | ||||||
| { | { | ||||||
|     #region 属性 |     #region 属性 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using System.Buffers; | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Foundation; | namespace ThingsGateway.Foundation; | ||||||
| @@ -235,5 +236,28 @@ public static class ByteBlockExtension | |||||||
|     } |     } | ||||||
|     #endregion AsSegment |     #endregion AsSegment | ||||||
|  |  | ||||||
|  |     #region ToString | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public static string ToString<TByteBlock>(this TByteBlock byteBlock, long offset, long length) where TByteBlock : IBytesReader | ||||||
|  |     { | ||||||
|  |         return byteBlock.TotalSequence.Slice(offset, length).ToString(Encoding.UTF8); | ||||||
|  |     } | ||||||
|  |     public static string ToString(this ReadOnlySequence<byte> byteBlock, Encoding encoding) | ||||||
|  |     { | ||||||
|  | # if NET6_0_OR_GREATER | ||||||
|  |         return encoding.GetString(byteBlock); | ||||||
|  | #else | ||||||
|  |         using ContiguousMemoryBuffer contiguousMemoryBuffer = new(byteBlock); | ||||||
|  |         return contiguousMemoryBuffer.Memory.Span.ToString(encoding); | ||||||
|  | #endif | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public static string ToString<TByteBlock>(this TByteBlock byteBlock, long offset) where TByteBlock : IBytesReader | ||||||
|  |     { | ||||||
|  |         return ToString(byteBlock, offset, byteBlock.BytesRead + byteBlock.BytesRemaining - offset); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |     #endregion ToString | ||||||
| } | } | ||||||
| @@ -0,0 +1,73 @@ | |||||||
|  | // ------------------------------------------------------------------------------ | ||||||
|  | // 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | // 此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | // 源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | // Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | // Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | // 使用文档:https://thingsgateway.cn/ | ||||||
|  | // QQ群:605534569 | ||||||
|  | // ------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | namespace ThingsGateway; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | public static class DisposableExtensions | ||||||
|  | { | ||||||
|  |     #region IDisposable | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 安全性释放(不用判断对象是否为空)。不会抛出任何异常。 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="dis"></param> | ||||||
|  |     /// <returns>释放状态,当对象为<see langword="null"/>,或者已被释放时,均会返回<see cref="Result.Success"/>,只有实际在释放时遇到异常时,才显示其他状态。</returns> | ||||||
|  |     public static async Task<Result> SafeDisposeAsync(this IAsyncDisposable dis) | ||||||
|  |     { | ||||||
|  |         if (dis == default) | ||||||
|  |         { | ||||||
|  |             return Result.Success; | ||||||
|  |         } | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             await dis.DisposeAsync().ConfigureAwait(false); | ||||||
|  |             return Result.Success; | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             return Result.FromException(ex); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #endregion IDisposable | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #if NET8_0_OR_GREATER | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 安全地取消 <see cref="CancellationTokenSource"/>,并返回操作结果。 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="tokenSource">要取消的 <see cref="CancellationTokenSource"/>。</param> | ||||||
|  |     /// <returns>一个 <see cref="Result"/> 对象,表示操作的结果。</returns> | ||||||
|  |     public static async Task<Result> SafeCancelAsync(this CancellationTokenSource tokenSource) | ||||||
|  |     { | ||||||
|  |         if (tokenSource is null) | ||||||
|  |         { | ||||||
|  |             return Result.Success; | ||||||
|  |         } | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             await tokenSource.CancelAsync().ConfigureAwait(false); | ||||||
|  |             return Result.Success; | ||||||
|  |         } | ||||||
|  |         catch (ObjectDisposedException) | ||||||
|  |         { | ||||||
|  |             return Result.Disposed; | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             return Result.FromException(ex); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | #endif | ||||||
|  | } | ||||||
| @@ -9,6 +9,7 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| namespace ThingsGateway.Foundation; | namespace ThingsGateway.Foundation; | ||||||
|  | #pragma warning disable CA1851 | ||||||
|  |  | ||||||
| public static class PackHelpers | public static class PackHelpers | ||||||
| { | { | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ public static class TextFileReader | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="directoryPath">目录路径</param> |     /// <param name="directoryPath">目录路径</param> | ||||||
|     /// <returns>包含文件信息的列表</returns> |     /// <returns>包含文件信息的列表</returns> | ||||||
|     public static OperResult<List<string>> GetFiles(string directoryPath) |     public static OperResult<List<string>> GetLogFilesAsync(string directoryPath) | ||||||
|     { |     { | ||||||
|         OperResult<List<string>> result = new(); // 初始化结果对象 |         OperResult<List<string>> result = new(); // 初始化结果对象 | ||||||
|         // 检查目录是否存在 |         // 检查目录是否存在 | ||||||
| @@ -91,7 +91,7 @@ public static class TextFileReader | |||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static OperResult<List<LogData>> LastLog(string file, int lineCount = 200) |     public static OperResult<List<LogData>> LastLogDataAsync(string file, int lineCount = 200) | ||||||
|     { |     { | ||||||
|         if (!File.Exists(file)) |         if (!File.Exists(file)) | ||||||
|             return new OperResult<List<LogData>>("The file path is invalid"); |             return new OperResult<List<LogData>>("The file path is invalid"); | ||||||
| @@ -104,7 +104,7 @@ public static class TextFileReader | |||||||
|             { |             { | ||||||
|                 var fileInfo = new FileInfo(file); |                 var fileInfo = new FileInfo(file); | ||||||
|                 var length = fileInfo.Length; |                 var length = fileInfo.Length; | ||||||
|                 var cacheKey = $"{nameof(TextFileReader)}_{nameof(LastLog)}_{file})"; |                 var cacheKey = $"{nameof(TextFileReader)}_{nameof(LastLogDataAsync)}_{file})"; | ||||||
|                 if (_cache.TryGetValue<LogDataCache>(cacheKey, out var cachedData)) |                 if (_cache.TryGetValue<LogDataCache>(cacheKey, out var cachedData)) | ||||||
|                 { |                 { | ||||||
|                     if (cachedData != null && cachedData.Length == length) |                     if (cachedData != null && cachedData.Length == length) | ||||||
|   | |||||||
| @@ -15,6 +15,27 @@ namespace ThingsGateway.Foundation; | |||||||
| /// </summary> | /// </summary> | ||||||
| public static class OperResultExtension | public static class OperResultExtension | ||||||
| { | { | ||||||
|  |  | ||||||
|  |     public static OperResult<object> GetOperResult(this IOperResult data) | ||||||
|  |     { | ||||||
|  |         OperResult<object> result = new(data); | ||||||
|  |         var operResultType = typeof(IOperResult<>); | ||||||
|  |         var interfaceType = data.GetType().GetInterfaces() | ||||||
|  |             .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == operResultType); | ||||||
|  |  | ||||||
|  |         if (interfaceType != null) | ||||||
|  |         { | ||||||
|  |             var contentProperty = interfaceType.GetProperty("Content"); | ||||||
|  |             if (contentProperty != null) | ||||||
|  |             { | ||||||
|  |                 result.Content = contentProperty.GetValue(data); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 转换对应类型 |     /// 转换对应类型 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://thingsgateway.cn/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Foundation; | ||||||
|  |  | ||||||
|  | public interface ITextFileReadService | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取指定目录下所有文件信息 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="directoryPath">目录路径</param> | ||||||
|  |     /// <returns>包含文件信息的列表</returns> | ||||||
|  |     public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath); | ||||||
|  |  | ||||||
|  |     public Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200); | ||||||
|  | } | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://thingsgateway.cn/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Foundation; | ||||||
|  |  | ||||||
|  | public class TextFileReadService : ITextFileReadService | ||||||
|  | { | ||||||
|  |     public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath) => Task.FromResult(TextFileReader.GetLogFilesAsync(directoryPath)); | ||||||
|  |  | ||||||
|  |     public Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200) => Task.FromResult(TextFileReader.LastLogDataAsync(file, lineCount)); | ||||||
|  | } | ||||||
| @@ -0,0 +1,109 @@ | |||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||||
|  | //  CSDN博客:https://blog.csdn.net/qq_40374647 | ||||||
|  | //  哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||||
|  | //  Github源代码仓库:https://github.com/RRQM | ||||||
|  | //  API首页:https://touchsocket.net/ | ||||||
|  | //  交流QQ群:234762506 | ||||||
|  | //  感谢您的下载和使用 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Foundation; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 具有释放的对象。内部实现了<see cref="GC.SuppressFinalize(object)"/>,但不包括析构函数相关。 | ||||||
|  | /// </summary> | ||||||
|  | public abstract partial class AsyncAndSyncDisposableObject : | ||||||
|  |    IDisposableObject, | ||||||
|  |    IAsyncDisposable | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 判断当前对象是否已经被释放。 | ||||||
|  |     /// 如果已经被释放,则抛出<see cref="ObjectDisposedException"/>异常。 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <exception cref="ObjectDisposedException">当对象已经被释放时抛出此异常</exception> | ||||||
|  |     [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |     protected void ThrowIfDisposed() | ||||||
|  |     { | ||||||
|  |         // 检查对象是否已经被释放 | ||||||
|  |         if (this.m_disposedValue) | ||||||
|  |         { | ||||||
|  |             // 如果对象已被释放,抛出ObjectDisposedException异常 | ||||||
|  |             throw new ObjectDisposedException($"The object instance with type {this.GetType().FullName} has been released"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private int m_count = 0; | ||||||
|  |     private int m_asyncCount = 0; | ||||||
|  |     /// <summary> | ||||||
|  |     /// 判断是否已释放。 | ||||||
|  |     /// </summary> | ||||||
|  |     private volatile bool m_disposedValue; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public bool DisposedValue => this.m_disposedValue; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 处置资源 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="disposing">一个值,表示是否释放托管资源</param> | ||||||
|  |     protected virtual void Dispose(bool disposing) | ||||||
|  |     { | ||||||
|  |         // 标记当前对象为已处置状态 | ||||||
|  |         this.m_disposedValue = true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/> | ||||||
|  |     /// </summary> | ||||||
|  |     public void Dispose() | ||||||
|  |     { | ||||||
|  |         if (this.DisposedValue) | ||||||
|  |         { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (Interlocked.Increment(ref this.m_count) == 1) | ||||||
|  |         { | ||||||
|  |             this.Dispose(disposing: true); | ||||||
|  |         } | ||||||
|  |         GC.SuppressFinalize(this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 处置资源 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="disposing">一个值,表示是否释放托管资源</param> | ||||||
|  |     protected virtual Task DisposeAsync(bool disposing) | ||||||
|  |     { | ||||||
|  |         // 标记当前对象为已处置状态 | ||||||
|  |         this.m_disposedValue = true; | ||||||
|  |         return Task.CompletedTask; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/> | ||||||
|  |     /// </summary> | ||||||
|  |     public async ValueTask DisposeAsync() | ||||||
|  |     { | ||||||
|  |         if (this.DisposedValue) | ||||||
|  |         { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         //if (Interlocked.Increment(ref this.m_count) == 1) | ||||||
|  |         //{ | ||||||
|  |         //    this.Dispose(disposing: true); | ||||||
|  |         //} | ||||||
|  |  | ||||||
|  |         if (Interlocked.Increment(ref this.m_asyncCount) == 1) | ||||||
|  |         { | ||||||
|  |             await this.DisposeAsync(disposing: true).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |         GC.SuppressFinalize(this); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,108 @@ | |||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||||
|  | //  CSDN博客:https://blog.csdn.net/qq_40374647 | ||||||
|  | //  哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||||
|  | //  Github源代码仓库:https://github.com/RRQM | ||||||
|  | //  API首页:https://touchsocket.net/ | ||||||
|  | //  交流QQ群:234762506 | ||||||
|  | //  感谢您的下载和使用 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Foundation; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// 具有释放的对象。内部实现了<see cref="GC.SuppressFinalize(object)"/>,但不包括析构函数相关。 | ||||||
|  | /// </summary> | ||||||
|  | public abstract partial class AsyncDisposableObject : | ||||||
|  |    //IDisposableObject, | ||||||
|  |    IAsyncDisposable | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 判断当前对象是否已经被释放。 | ||||||
|  |     /// 如果已经被释放,则抛出<see cref="ObjectDisposedException"/>异常。 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <exception cref="ObjectDisposedException">当对象已经被释放时抛出此异常</exception> | ||||||
|  |     [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |     protected void ThrowIfDisposed() | ||||||
|  |     { | ||||||
|  |         // 检查对象是否已经被释放 | ||||||
|  |         if (this.m_disposedValue) | ||||||
|  |         { | ||||||
|  |             // 如果对象已被释放,抛出ObjectDisposedException异常 | ||||||
|  |             throw new ObjectDisposedException($"The object instance with type {this.GetType().FullName} has been released"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private int m_asyncCount = 0; | ||||||
|  |     /// <summary> | ||||||
|  |     /// 判断是否已释放。 | ||||||
|  |     /// </summary> | ||||||
|  |     private volatile bool m_disposedValue; | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public bool DisposedValue => this.m_disposedValue; | ||||||
|  |  | ||||||
|  |     ///// <summary> | ||||||
|  |     ///// 处置资源 | ||||||
|  |     ///// </summary> | ||||||
|  |     ///// <param name="disposing">一个值,表示是否释放托管资源</param> | ||||||
|  |     //protected virtual void Dispose(bool disposing) | ||||||
|  |     //{ | ||||||
|  |     //    // 标记当前对象为已处置状态 | ||||||
|  |     //    this.m_disposedValue = true; | ||||||
|  |     //} | ||||||
|  |  | ||||||
|  |     ///// <summary> | ||||||
|  |     ///// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/> | ||||||
|  |     ///// </summary> | ||||||
|  |     //public void Dispose() | ||||||
|  |     //{ | ||||||
|  |     //    if (this.DisposedValue) | ||||||
|  |     //    { | ||||||
|  |     //        return; | ||||||
|  |     //    } | ||||||
|  |  | ||||||
|  |     //    if (Interlocked.Increment(ref this.m_count) == 1) | ||||||
|  |     //    { | ||||||
|  |     //        this.Dispose(disposing: true); | ||||||
|  |     //    } | ||||||
|  |     //    GC.SuppressFinalize(this); | ||||||
|  |     //} | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 处置资源 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="disposing">一个值,表示是否释放托管资源</param> | ||||||
|  |     protected virtual Task DisposeAsync(bool disposing) | ||||||
|  |     { | ||||||
|  |         // 标记当前对象为已处置状态 | ||||||
|  |         this.m_disposedValue = true; | ||||||
|  |         return Task.CompletedTask; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/> | ||||||
|  |     /// </summary> | ||||||
|  |     public async ValueTask DisposeAsync() | ||||||
|  |     { | ||||||
|  |         if (this.DisposedValue) | ||||||
|  |         { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         //if (Interlocked.Increment(ref this.m_count) == 1) | ||||||
|  |         //{ | ||||||
|  |         //    this.Dispose(disposing: true); | ||||||
|  |         //} | ||||||
|  |  | ||||||
|  |         if (Interlocked.Increment(ref this.m_asyncCount) == 1) | ||||||
|  |         { | ||||||
|  |             await this.DisposeAsync(disposing: true).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |         GC.SuppressFinalize(this); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -33,6 +33,8 @@ public class AsyncReadWriteLock | |||||||
|         { |         { | ||||||
|             Interlocked.Increment(ref _readerCount); |             Interlocked.Increment(ref _readerCount); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             // 第一个读者需要获取写入锁,防止写操作 |             // 第一个读者需要获取写入锁,防止写操作 | ||||||
|             await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); |             await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
| @@ -54,42 +56,47 @@ public class AsyncReadWriteLock | |||||||
|         { |         { | ||||||
|             var cancellationTokenSource = _cancellationTokenSource; |             var cancellationTokenSource = _cancellationTokenSource; | ||||||
|             _cancellationTokenSource = new(); |             _cancellationTokenSource = new(); | ||||||
|             await cancellationTokenSource.CancelAsync().ConfigureAwait(false); // 取消读取 |             await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取 | ||||||
|             cancellationTokenSource.SafeDispose(); |             cancellationTokenSource.SafeDispose(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return new Writer(this); |         return new Writer(this); | ||||||
|     } |     } | ||||||
|  |     private object lockObject = new(); | ||||||
|     private void ReleaseWriter() |     private void ReleaseWriter() | ||||||
|     { |     { | ||||||
|  |  | ||||||
|         var writerCount = Interlocked.Decrement(ref _writerCount); |         var writerCount = Interlocked.Decrement(ref _writerCount); | ||||||
|  |  | ||||||
|  |         // 每次释放写时,总是唤醒至少一个读 | ||||||
|  |         _readerLock.Set(); | ||||||
|  |  | ||||||
|         if (writerCount == 0) |         if (writerCount == 0) | ||||||
|         { |         { | ||||||
|             var resetEvent = _readerLock; |             var resetEvent = _readerLock; | ||||||
|             _readerLock = new(false); |             //_readerLock = new(false); | ||||||
|             Interlocked.Exchange(ref _writeSinceLastReadCount, 0); |             Interlocked.Exchange(ref _writeSinceLastReadCount, 0); | ||||||
|             resetEvent.SetAll(); |             resetEvent.SetAll(); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|  |             lock (lockObject) | ||||||
|             // 读写占空比, 用于控制写操作与读操作的比率。该比率 n 次写入操作会执行一次读取操作。即使在应用程序执行大量的连续写入操作时,也必须确保足够的读取数据处理时间。相对于更加均衡的读写数据流而言,该特点使得外部写入可连续无顾忌操作 |  | ||||||
|  |  | ||||||
|             if (_writeReadRatio > 0) |  | ||||||
|             { |             { | ||||||
|                 if (Interlocked.Read(ref _readerCount) > 0) |  | ||||||
|  |                 // 读写占空比, 用于控制写操作与读操作的比率。该比率 n 次写入操作会执行一次读取操作。即使在应用程序执行大量的连续写入操作时,也必须确保足够的读取数据处理时间。相对于更加均衡的读写数据流而言,该特点使得外部写入可连续无顾忌操作 | ||||||
|  |                 if (_writeReadRatio > 0) | ||||||
|                 { |                 { | ||||||
|                     var count = Interlocked.Increment(ref _writeSinceLastReadCount); |                     var count = Interlocked.Increment(ref _writeSinceLastReadCount); | ||||||
|                     if (count >= _writeReadRatio) |                     if (count >= _writeReadRatio) | ||||||
|                     { |                     { | ||||||
|                         Interlocked.Exchange(ref _writeSinceLastReadCount, 0); |                         Interlocked.Exchange(ref _writeSinceLastReadCount, 0); | ||||||
|                         _readerLock.Set(); |                         //_readerLock.Set(); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |                 else | ||||||
|             else |                 { | ||||||
|             { |                     //_readerLock.Set(); | ||||||
|                 _readerLock.Set(); |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -0,0 +1,57 @@ | |||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://thingsgateway.cn/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Gateway.Application; | ||||||
|  |  | ||||||
|  | public class LinkedCancellationTokenSourceCache : IDisposable | ||||||
|  | { | ||||||
|  |     private CancellationTokenSource? _cachedCts; | ||||||
|  |     private CancellationToken _token1; | ||||||
|  |     private CancellationToken _token2; | ||||||
|  |     private readonly object _lock = new(); | ||||||
|  |     ~LinkedCancellationTokenSourceCache() | ||||||
|  |     { | ||||||
|  |         Dispose(); | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取一个 CancellationTokenSource,它是由两个 token 链接而成的。 | ||||||
|  |     /// 会尝试复用之前缓存的 CTS,前提是两个 token 仍然相同且未取消。 | ||||||
|  |     /// </summary> | ||||||
|  |     public CancellationTokenSource GetLinkedTokenSource(CancellationToken token1, CancellationToken token2) | ||||||
|  |     { | ||||||
|  |         lock (_lock) | ||||||
|  |         { | ||||||
|  |             // 如果缓存的 CTS 已经取消或 Dispose,或者 token 不同,重新创建 | ||||||
|  |             if (_cachedCts?.IsCancellationRequested != false || | ||||||
|  |                 !_token1.Equals(token1) || !_token2.Equals(token2)) | ||||||
|  |             { | ||||||
|  |                 _cachedCts?.Dispose(); | ||||||
|  |  | ||||||
|  |                 _cachedCts = CancellationTokenSource.CreateLinkedTokenSource(token1, token2); | ||||||
|  |                 _token1 = token1; | ||||||
|  |                 _token2 = token2; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return _cachedCts; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void Dispose() | ||||||
|  |     { | ||||||
|  |         lock (_lock) | ||||||
|  |         { | ||||||
|  |             _cachedCts?.Dispose(); | ||||||
|  |             _cachedCts = null!; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -10,6 +10,7 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | |||||||
|     private int _interval10MS = 10; |     private int _interval10MS = 10; | ||||||
|     private string _interval; |     private string _interval; | ||||||
|     private readonly Func<object?, CancellationToken, Task> _taskFunc; |     private readonly Func<object?, CancellationToken, Task> _taskFunc; | ||||||
|  |     private readonly Func<object?, CancellationToken, ValueTask> _valueTaskFunc; | ||||||
|     private readonly Action<object?, CancellationToken> _taskAction; |     private readonly Action<object?, CancellationToken> _taskAction; | ||||||
|     private readonly CancellationToken _token; |     private readonly CancellationToken _token; | ||||||
|     private TimerX? _timer; |     private TimerX? _timer; | ||||||
| @@ -28,6 +29,17 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | |||||||
|         _taskFunc = taskFunc; |         _taskFunc = taskFunc; | ||||||
|         _token = token; |         _token = token; | ||||||
|     } |     } | ||||||
|  |     public CronScheduledTask(string interval, Func<object?, CancellationToken, ValueTask> taskFunc, object? state, ILog log, CancellationToken token) | ||||||
|  |     { | ||||||
|  |         _interval = interval; | ||||||
|  |         LogMessage = log; | ||||||
|  |         _state = state; | ||||||
|  |         _valueTaskFunc = taskFunc; | ||||||
|  |         _token = token; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     public CronScheduledTask(string interval, Action<object?, CancellationToken> taskAction, object? state, ILog log, CancellationToken token) |     public CronScheduledTask(string interval, Action<object?, CancellationToken> taskAction, object? state, ILog log, CancellationToken token) | ||||||
|     { |     { | ||||||
|         _interval = interval; |         _interval = interval; | ||||||
| @@ -51,14 +63,14 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | |||||||
|         if (Check()) return; |         if (Check()) return; | ||||||
|         if (_taskAction != null) |         if (_taskAction != null) | ||||||
|             _timer = new TimerX(TimerCallback, _state, _interval, nameof(IScheduledTask)) { Async = true }; |             _timer = new TimerX(TimerCallback, _state, _interval, nameof(IScheduledTask)) { Async = true }; | ||||||
|         else if (_taskFunc != null) |         else if (_taskFunc != null || _valueTaskFunc != null) | ||||||
|             _timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(IScheduledTask)) { Async = true }; |             _timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(IScheduledTask)) { Async = true }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task TimerCallbackAsync(object? state) |     private async ValueTask TimerCallbackAsync(object? state) | ||||||
|     { |     { | ||||||
|         if (Check()) return; |         if (Check()) return; | ||||||
|         if (_taskFunc == null) |         if (_taskFunc == null && _valueTaskFunc == null) | ||||||
|         { |         { | ||||||
|             Dispose(); |             Dispose(); | ||||||
|             return; |             return; | ||||||
| @@ -74,7 +86,10 @@ public class CronScheduledTask : DisposeBase, IScheduledTask | |||||||
|  |  | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             await _taskFunc(state, _token).ConfigureAwait(false); |             if (_taskFunc != null) | ||||||
|  |                 await _taskFunc(state, _token).ConfigureAwait(false); | ||||||
|  |             else if (_valueTaskFunc != null) | ||||||
|  |                 await _valueTaskFunc(state, _token).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|         catch (OperationCanceledException) |         catch (OperationCanceledException) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte | |||||||
|     private int _interval10MS = 10; |     private int _interval10MS = 10; | ||||||
|     public int IntervalMS { get; } |     public int IntervalMS { get; } | ||||||
|     private readonly Func<object?, CancellationToken, Task> _taskFunc; |     private readonly Func<object?, CancellationToken, Task> _taskFunc; | ||||||
|  |     private readonly Func<object?, CancellationToken, ValueTask> _valueTaskFunc; | ||||||
|     private readonly CancellationToken _token; |     private readonly CancellationToken _token; | ||||||
|     private TimerX? _timer; |     private TimerX? _timer; | ||||||
|     private object? _state; |     private object? _state; | ||||||
| @@ -26,6 +27,14 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte | |||||||
|         _taskFunc = taskFunc; |         _taskFunc = taskFunc; | ||||||
|         _token = token; |         _token = token; | ||||||
|     } |     } | ||||||
|  |     public ScheduledAsyncTask(int interval, Func<object?, CancellationToken, ValueTask> taskFunc, object? state, ILog log, CancellationToken token) | ||||||
|  |     { | ||||||
|  |         IntervalMS = interval; | ||||||
|  |         LogMessage = log; | ||||||
|  |         _state = state; | ||||||
|  |         _valueTaskFunc = taskFunc; | ||||||
|  |         _token = token; | ||||||
|  |     } | ||||||
|     private bool Check() |     private bool Check() | ||||||
|     { |     { | ||||||
|         if (_token.IsCancellationRequested) |         if (_token.IsCancellationRequested) | ||||||
| @@ -42,11 +51,17 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte | |||||||
|             _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(IScheduledTask)) { Async = true }; |             _timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(IScheduledTask)) { Async = true }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task DoAsync(object? state) |     private async ValueTask DoAsync(object? state) | ||||||
|     { |     { | ||||||
|         if (Check()) |         if (Check()) | ||||||
|             return; |             return; | ||||||
|  |  | ||||||
|  |         if (_taskFunc == null && _valueTaskFunc == null) | ||||||
|  |         { | ||||||
|  |             Dispose(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         Interlocked.Increment(ref _pendingTriggers); |         Interlocked.Increment(ref _pendingTriggers); | ||||||
|  |  | ||||||
|         if (Interlocked.Exchange(ref _isRunning, 1) == 1) |         if (Interlocked.Exchange(ref _isRunning, 1) == 1) | ||||||
| @@ -55,9 +70,13 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte | |||||||
|         // 减少一个触发次数 |         // 减少一个触发次数 | ||||||
|         Interlocked.Decrement(ref _pendingTriggers); |         Interlocked.Decrement(ref _pendingTriggers); | ||||||
|  |  | ||||||
|  |  | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             await _taskFunc(state, _token).ConfigureAwait(false); |             if (_taskFunc != null) | ||||||
|  |                 await _taskFunc(state, _token).ConfigureAwait(false); | ||||||
|  |             else if (_valueTaskFunc != null) | ||||||
|  |                 await _valueTaskFunc(state, _token).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|         catch (OperationCanceledException) |         catch (OperationCanceledException) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -14,6 +14,18 @@ public static class ScheduledTaskHelper | |||||||
|             return new CronScheduledTask(interval, func, state, log, cancellationToken); |             return new CronScheduledTask(interval, func, state, log, cancellationToken); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     public static IScheduledTask GetTask(string interval, Func<object?, CancellationToken, ValueTask> func, object? state, TouchSocket.Core.ILog log, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         if (int.TryParse(interval, out int intervalV)) | ||||||
|  |         { | ||||||
|  |             var intervalMilliseconds = intervalV < 10 ? 10 : intervalV; | ||||||
|  |             return new ScheduledAsyncTask(intervalMilliseconds, func, state, log, cancellationToken); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             return new CronScheduledTask(interval, func, state, log, cancellationToken); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     public static IScheduledTask GetTask(string interval, Action<object?, CancellationToken> action, object? state, TouchSocket.Core.ILog log, CancellationToken cancellationToken) |     public static IScheduledTask GetTask(string interval, Action<object?, CancellationToken> action, object? state, TouchSocket.Core.ILog log, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (int.TryParse(interval, out int intervalV)) |         if (int.TryParse(interval, out int intervalV)) | ||||||
|   | |||||||
| @@ -35,6 +35,12 @@ public static class ExportString | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public static string VariableName => Localizer["VariableName"]; |     public static string VariableName => Localizer["VariableName"]; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 变量报警表名称 | ||||||
|  |     /// </summary> | ||||||
|  |     public static string AlarmName => Localizer["AlarmName"]; | ||||||
|  |  | ||||||
|     public static IStringLocalizer localizer; |     public static IStringLocalizer localizer; | ||||||
|     public static IStringLocalizer Localizer |     public static IStringLocalizer Localizer | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -16,20 +16,20 @@ namespace ThingsGateway.Gateway.Application; | |||||||
| [ThingsGateway.DependencyInjection.SuppressSniffer] | [ThingsGateway.DependencyInjection.SuppressSniffer] | ||||||
| public static class ThingsGatewayCacheConst | public static class ThingsGatewayCacheConst | ||||||
| { | { | ||||||
|     /// <summary> |     ///// <summary> | ||||||
|     /// 通道 |     ///// 通道 | ||||||
|     /// </summary> |     ///// </summary> | ||||||
|     public const string Cache_Channel = $"{Cache_Prefix}Cache_Channel:List"; |     //public const string Cache_Channel = $"{Cache_Prefix}Cache_Channel:List"; | ||||||
|  |  | ||||||
|     /// <summary> |     ///// <summary> | ||||||
|     /// device |     ///// device | ||||||
|     /// </summary> |     ///// </summary> | ||||||
|     public const string Cache_Device = $"{Cache_Prefix}Cache_Device:List"; |     //public const string Cache_Device = $"{Cache_Prefix}Cache_Device:List"; | ||||||
|  |  | ||||||
|     /// <summary> |     ///// <summary> | ||||||
|     /// variable |     ///// variable | ||||||
|     /// </summary> |     ///// </summary> | ||||||
|     public const string Cache_Variable = $"{Cache_Prefix}Cache_Variable:IdNameList"; |     //public const string Cache_Variable = $"{Cache_Prefix}Cache_Variable:IdNameList"; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 前缀 |     /// 前缀 | ||||||
|   | |||||||
| @@ -1,43 +0,0 @@ | |||||||
| //------------------------------------------------------------------------------ |  | ||||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 |  | ||||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 |  | ||||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 |  | ||||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway |  | ||||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway |  | ||||||
| //  使用文档:https://thingsgateway.cn/ |  | ||||||
| //  QQ群:605534569 |  | ||||||
| //------------------------------------------------------------------------------ |  | ||||||
|  |  | ||||||
| using Microsoft.AspNetCore.Authorization; |  | ||||||
| using Microsoft.AspNetCore.Mvc; |  | ||||||
|  |  | ||||||
| using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.Management; |  | ||||||
|  |  | ||||||
| [ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)] |  | ||||||
| [Route("openApi/autoUpdate")] |  | ||||||
| [RolePermission] |  | ||||||
| [RequestAudit] |  | ||||||
| [ApiController] |  | ||||||
| [Authorize(AuthenticationSchemes = "Bearer")] |  | ||||||
| public class AutoUpdateController : ControllerBase |  | ||||||
| { |  | ||||||
|     private IUpdateZipFileHostedService _updateZipFileService; |  | ||||||
|     public AutoUpdateController(IUpdateZipFileHostedService updateZipFileService) |  | ||||||
|     { |  | ||||||
|         _updateZipFileService = updateZipFileService; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 检查更新 |  | ||||||
|     /// </summary> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     [HttpPost("update")] |  | ||||||
|     public async Task Update() |  | ||||||
|     { |  | ||||||
|         var data = await _updateZipFileService.GetList().ConfigureAwait(false); |  | ||||||
|         if (data.Count != 0) |  | ||||||
|             await _updateZipFileService.Update(data.OrderByDescending(a => a.Version).FirstOrDefault()).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -15,13 +15,10 @@ using Microsoft.AspNetCore.Http; | |||||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||||
|  |  | ||||||
| using System.ComponentModel; | using System.ComponentModel; | ||||||
| using System.ComponentModel.DataAnnotations; |  | ||||||
| using System.IO.Ports; |  | ||||||
|  |  | ||||||
| using ThingsGateway.FriendlyException; | using ThingsGateway.FriendlyException; | ||||||
|  |  | ||||||
| using TouchSocket.Core; | using TouchSocket.Rpc; | ||||||
| using TouchSocket.Sockets; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Gateway.Application; | ||||||
|  |  | ||||||
| @@ -34,7 +31,9 @@ namespace ThingsGateway.Gateway.Application; | |||||||
| [RequestAudit] | [RequestAudit] | ||||||
| [ApiController] | [ApiController] | ||||||
| [Authorize(AuthenticationSchemes = "Bearer")] | [Authorize(AuthenticationSchemes = "Bearer")] | ||||||
| public class ControlController : ControllerBase | [TouchSocket.WebApi.Router("/miniapi/[api]/[action]")] | ||||||
|  | [TouchSocket.WebApi.EnableCors("cors")] | ||||||
|  | public class ControlController : ControllerBase, IRpcServer | ||||||
| { | { | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -43,6 +42,7 @@ public class ControlController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpPost("removeAllCache")] |     [HttpPost("removeAllCache")] | ||||||
|     [DisplayName("清空全部缓存")] |     [DisplayName("清空全部缓存")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|     public void RemoveAllCache() |     public void RemoveAllCache() | ||||||
|     { |     { | ||||||
|         App.CacheService.Clear(); |         App.CacheService.Clear(); | ||||||
| @@ -54,6 +54,7 @@ public class ControlController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpPost("removeCache")] |     [HttpPost("removeCache")] | ||||||
|     [DisplayName("删除通道/设备缓存")] |     [DisplayName("删除通道/设备缓存")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|     public void RemoveCache() |     public void RemoveCache() | ||||||
|     { |     { | ||||||
|         App.GetService<IDeviceService>().DeleteDeviceFromCache(); |         App.GetService<IDeviceService>().DeleteDeviceFromCache(); | ||||||
| @@ -66,6 +67,7 @@ public class ControlController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpPost("pauseBusinessThread")] |     [HttpPost("pauseBusinessThread")] | ||||||
|     [DisplayName("控制设备线程启停")] |     [DisplayName("控制设备线程启停")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|     public async Task PauseDeviceThreadAsync(long id, bool pause) |     public async Task PauseDeviceThreadAsync(long id, bool pause) | ||||||
|     { |     { | ||||||
|         if (GlobalData.IdDevices.TryGetValue(id, out var device)) |         if (GlobalData.IdDevices.TryGetValue(id, out var device)) | ||||||
| @@ -85,6 +87,7 @@ public class ControlController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpPost("restartScopeThread")] |     [HttpPost("restartScopeThread")] | ||||||
|     [DisplayName("重启当前机构线程")] |     [DisplayName("重启当前机构线程")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|     public async Task RestartScopeThread() |     public async Task RestartScopeThread() | ||||||
|     { |     { | ||||||
|         var data = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false); |         var data = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false); | ||||||
| @@ -97,6 +100,7 @@ public class ControlController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpPost("restartAllThread")] |     [HttpPost("restartAllThread")] | ||||||
|     [DisplayName("重启全部线程")] |     [DisplayName("重启全部线程")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|     public async Task RestartAllThread() |     public async Task RestartAllThread() | ||||||
|     { |     { | ||||||
|         await GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.IdChannels.Values).ConfigureAwait(false); |         await GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.IdChannels.Values).ConfigureAwait(false); | ||||||
| @@ -108,6 +112,7 @@ public class ControlController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpPost("restartThread")] |     [HttpPost("restartThread")] | ||||||
|     [DisplayName("重启设备线程")] |     [DisplayName("重启设备线程")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|     public async Task RestartDeviceThreadAsync(long deviceId) |     public async Task RestartDeviceThreadAsync(long deviceId) | ||||||
|     { |     { | ||||||
|         if (GlobalData.IdDevices.TryGetValue(deviceId, out var deviceRuntime)) |         if (GlobalData.IdDevices.TryGetValue(deviceId, out var deviceRuntime)) | ||||||
| @@ -126,7 +131,8 @@ public class ControlController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpPost("writeVariables")] |     [HttpPost("writeVariables")] | ||||||
|     [DisplayName("写入变量")] |     [DisplayName("写入变量")] | ||||||
|     public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody][TouchSocket.WebApi.FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas) | ||||||
|     { |     { | ||||||
|         foreach (var deviceData in deviceDatas) |         foreach (var deviceData in deviceDatas) | ||||||
|         { |         { | ||||||
| @@ -145,9 +151,10 @@ public class ControlController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpPost("batchSaveChannel")] |     [HttpPost("batchSaveChannel")] | ||||||
|     [DisplayName("保存通道")] |     [DisplayName("保存通道")] | ||||||
|     public Task<bool> BatchSaveChannelAsync([FromBody] List<ChannelInput> channels, ItemChangedType type, bool restart = true) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public Task<bool> BatchSaveChannelAsync([FromBody][TouchSocket.WebApi.FromBody] List<Channel> channels, ItemChangedType type, bool restart = true) | ||||||
|     { |     { | ||||||
|         return GlobalData.ChannelRuntimeService.BatchSaveChannelAsync(channels.AdaptListChannel(), type, restart); |         return GlobalData.ChannelRuntimeService.BatchSaveChannelAsync(channels, type, restart); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -155,9 +162,10 @@ public class ControlController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpPost("batchSaveDevice")] |     [HttpPost("batchSaveDevice")] | ||||||
|     [DisplayName("保存设备")] |     [DisplayName("保存设备")] | ||||||
|     public Task<bool> BatchSaveDeviceAsync([FromBody] List<DeviceInput> devices, ItemChangedType type, bool restart = true) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public Task<bool> BatchSaveDeviceAsync([FromBody][TouchSocket.WebApi.FromBody] List<Device> devices, ItemChangedType type, bool restart = true) | ||||||
|     { |     { | ||||||
|         return GlobalData.DeviceRuntimeService.BatchSaveDeviceAsync(devices.AdaptListDevice(), type, restart); |         return GlobalData.DeviceRuntimeService.BatchSaveDeviceAsync(devices, type, restart); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -165,9 +173,10 @@ public class ControlController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpPost("batchSaveVariable")] |     [HttpPost("batchSaveVariable")] | ||||||
|     [DisplayName("保存变量")] |     [DisplayName("保存变量")] | ||||||
|     public Task<bool> BatchSaveVariableAsync([FromBody] List<VariableInput> variables, ItemChangedType type, bool restart = true) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public Task<bool> BatchSaveVariableAsync([FromBody][TouchSocket.WebApi.FromBody] List<Variable> variables, ItemChangedType type, bool restart = true) | ||||||
|     { |     { | ||||||
|         return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables.AdaptListVariable(), type, restart, default); |         return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables, type, restart, default); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -175,7 +184,8 @@ public class ControlController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpPost("deleteChannel")] |     [HttpPost("deleteChannel")] | ||||||
|     [DisplayName("删除通道")] |     [DisplayName("删除通道")] | ||||||
|     public Task<bool> DeleteChannelAsync([FromBody] List<long> ids, bool restart = true) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public Task<bool> DeleteChannelAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true) | ||||||
|     { |     { | ||||||
|         if (ids == null || ids.Count == 0) ids = GlobalData.IdChannels.Keys.ToList(); |         if (ids == null || ids.Count == 0) ids = GlobalData.IdChannels.Keys.ToList(); | ||||||
|         return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart, default); |         return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart, default); | ||||||
| @@ -186,7 +196,8 @@ public class ControlController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpPost("deleteDevice")] |     [HttpPost("deleteDevice")] | ||||||
|     [DisplayName("删除设备")] |     [DisplayName("删除设备")] | ||||||
|     public Task<bool> DeleteDeviceAsync([FromBody] List<long> ids, bool restart = true) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public Task<bool> DeleteDeviceAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true) | ||||||
|     { |     { | ||||||
|         if (ids == null || ids.Count == 0) ids = GlobalData.IdDevices.Keys.ToList(); |         if (ids == null || ids.Count == 0) ids = GlobalData.IdDevices.Keys.ToList(); | ||||||
|         return GlobalData.DeviceRuntimeService.DeleteDeviceAsync(ids, restart, default); |         return GlobalData.DeviceRuntimeService.DeleteDeviceAsync(ids, restart, default); | ||||||
| @@ -197,7 +208,8 @@ public class ControlController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpPost("deleteVariable")] |     [HttpPost("deleteVariable")] | ||||||
|     [DisplayName("删除变量")] |     [DisplayName("删除变量")] | ||||||
|     public Task<bool> DeleteVariableAsync([FromBody] List<long> ids, bool restart = true) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public Task<bool> DeleteVariableAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true) | ||||||
|     { |     { | ||||||
|         if (ids == null || ids.Count == 0) ids = GlobalData.IdVariables.Keys.ToList(); |         if (ids == null || ids.Count == 0) ids = GlobalData.IdVariables.Keys.ToList(); | ||||||
|         return GlobalData.VariableRuntimeService.DeleteVariableAsync(ids, restart, default); |         return GlobalData.VariableRuntimeService.DeleteVariableAsync(ids, restart, default); | ||||||
| @@ -208,6 +220,7 @@ public class ControlController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpPost("insertTestData")] |     [HttpPost("insertTestData")] | ||||||
|     [DisplayName("增加测试数据")] |     [DisplayName("增加测试数据")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|     public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart = true) |     public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart = true) | ||||||
|     { |     { | ||||||
|         return GlobalData.VariableRuntimeService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart, default); |         return GlobalData.VariableRuntimeService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart, default); | ||||||
| @@ -220,6 +233,7 @@ public class ControlController : ControllerBase | |||||||
|     [HttpPost("checkRealAlarm")] |     [HttpPost("checkRealAlarm")] | ||||||
|     [RequestAudit] |     [RequestAudit] | ||||||
|     [DisplayName("确认实时报警")] |     [DisplayName("确认实时报警")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|     public async Task CheckRealAlarm(long variableId) |     public async Task CheckRealAlarm(long variableId) | ||||||
|     { |     { | ||||||
|         if (GlobalData.ReadOnlyRealAlarmIdVariables.TryGetValue(variableId, out var variable)) |         if (GlobalData.ReadOnlyRealAlarmIdVariables.TryGetValue(variableId, out var variable)) | ||||||
| @@ -229,552 +243,3 @@ public class ControlController : ControllerBase | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| public class ChannelInput |  | ||||||
| { |  | ||||||
|     /// <summary> |  | ||||||
|     /// 主键Id |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual long Id { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 通道名称 |  | ||||||
|     /// </summary> |  | ||||||
|     [Required] |  | ||||||
|     public virtual string Name { get; set; } |  | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |  | ||||||
|     public virtual ChannelTypeEnum ChannelType { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 插件名称 |  | ||||||
|     /// </summary> |  | ||||||
|     [Required] |  | ||||||
|     public virtual string PluginName { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual bool Enable { get; set; } = true; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// LogLevel |  | ||||||
|     /// </summary> |  | ||||||
|     public LogLevel LogLevel { get; set; } = LogLevel.Info; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 远程地址,可由<see cref="IPHost"/> 与 <see cref="string"/> 相互转化 |  | ||||||
|     /// </summary> |  | ||||||
|     [UriValidation] |  | ||||||
|     public virtual string RemoteUrl { get; set; } = "127.0.0.1:502"; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 本地地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化 |  | ||||||
|     /// </summary> |  | ||||||
|     [UriValidation] |  | ||||||
|     public virtual string BindUrl { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// COM |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual string PortName { get; set; } = "COM1"; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 波特率 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual int BaudRate { get; set; } = 9600; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 数据位 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual int DataBits { get; set; } = 8; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 校验位 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual Parity Parity { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 停止位 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual StopBits StopBits { get; set; } = StopBits.One; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// DtrEnable |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual bool DtrEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// RtsEnable |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual bool RtsEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// StreamAsync |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual bool StreamAsync { get; set; } = false; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 缓存超时 |  | ||||||
|     /// </summary> |  | ||||||
|     [MinValue(100)] |  | ||||||
|     public virtual int CacheTimeout { get; set; } = 500; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 连接超时 |  | ||||||
|     /// </summary> |  | ||||||
|     [MinValue(100)] |  | ||||||
|     public virtual ushort ConnectTimeout { get; set; } = 3000; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 最大并发数 |  | ||||||
|     /// </summary> |  | ||||||
|     [MinValue(1)] |  | ||||||
|     public virtual int MaxConcurrentCount { get; set; } = 1; |  | ||||||
|  |  | ||||||
|     public virtual int MaxClientCount { get; set; } = 10000; |  | ||||||
|     public virtual int CheckClearTime { get; set; } = 120000; |  | ||||||
|     public virtual string Heartbeat { get; set; } = "Heartbeat"; |  | ||||||
|  |  | ||||||
|     #region dtu终端 |  | ||||||
|  |  | ||||||
|     public virtual int HeartbeatTime { get; set; } = 60000; |  | ||||||
|     public virtual string DtuId { get; set; } |  | ||||||
|  |  | ||||||
|     #endregion |  | ||||||
|  |  | ||||||
|     public virtual DtuSeviceType DtuSeviceType { get; set; } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| public class DeviceInput : IValidatableObject |  | ||||||
| { |  | ||||||
|     public long Id { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 名称 |  | ||||||
|     /// </summary> |  | ||||||
|     [Required] |  | ||||||
|     [RegularExpression(@"^[^.]*$", ErrorMessage = "The field {0} cannot contain a dot ('.')")] |  | ||||||
|     public virtual string Name { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 描述 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Description { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 通道 |  | ||||||
|     /// </summary> |  | ||||||
|     [MinValue(1)] |  | ||||||
|     [Required] |  | ||||||
|     public virtual long ChannelId { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 默认执行间隔,支持corn表达式 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual string IntervalTime { get; set; } = "1000"; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 设备使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual bool Enable { get; set; } = true; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// LogLevel |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual TouchSocket.Core.LogLevel LogLevel { get; set; } = TouchSocket.Core.LogLevel.Info; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 设备属性Json |  | ||||||
|     /// </summary> |  | ||||||
|     public Dictionary<string, string>? DevicePropertys { get; set; } = new(); |  | ||||||
|  |  | ||||||
|     #region 冗余配置 |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 启用冗余 |  | ||||||
|     /// </summary> |  | ||||||
|     public bool RedundantEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 冗余设备Id,只能选择相同驱动 |  | ||||||
|     /// </summary> |  | ||||||
|     public long? RedundantDeviceId { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 冗余模式 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual RedundantSwitchTypeEnum RedundantSwitchType { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 冗余扫描间隔 |  | ||||||
|     /// </summary> |  | ||||||
|     [MinValue(30000)] |  | ||||||
|     public virtual int RedundantScanIntervalTime { get; set; } = 30000; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 冗余切换判断脚本,返回true则切换冗余设备 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual string RedundantScript { get; set; } |  | ||||||
|  |  | ||||||
|     #endregion 冗余配置 |  | ||||||
|  |  | ||||||
|     #region 备用字段 |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark1 { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark2 { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark3 { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark4 { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark5 { get; set; } |  | ||||||
|  |  | ||||||
|     #endregion 备用字段 |  | ||||||
|  |  | ||||||
|     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) |  | ||||||
|     { |  | ||||||
|         if (RedundantEnable && RedundantDeviceId == null) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("When enable redundancy, you must select a redundant device.", new[] { nameof(RedundantEnable), nameof(RedundantDeviceId) }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| public class VariableInput : IValidatableObject |  | ||||||
| { |  | ||||||
|     public virtual long Id { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 设备 |  | ||||||
|     /// </summary> |  | ||||||
|     [Required] |  | ||||||
|     public virtual long DeviceId { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 变量名称 |  | ||||||
|     /// </summary> |  | ||||||
|     [Required] |  | ||||||
|     public virtual string Name { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 描述 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Description { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 单位 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual string? Unit { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 间隔时间 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual string? IntervalTime { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 变量地址,可能带有额外的信息,比如<see cref="DataFormatEnum"/> ,以;分割 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? RegisterAddress { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 数组长度 |  | ||||||
|     /// </summary> |  | ||||||
|     public int? ArrayLength { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 其他方法,若不为空,此时RegisterAddress为方法参数 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? OtherMethod { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual bool Enable { get; set; } = true; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 读写权限 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual ProtectTypeEnum ProtectType { get; set; } = ProtectTypeEnum.ReadWrite; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 数据类型 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 读取表达式 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual string? ReadExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 写入表达式 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual string? WriteExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 是否允许远程Rpc写入 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual bool RpcWriteEnable { get; set; } = true; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 初始值 |  | ||||||
|     /// </summary> |  | ||||||
|     public object? InitValue |  | ||||||
|     { |  | ||||||
|         get |  | ||||||
|         { |  | ||||||
|             return _value; |  | ||||||
|         } |  | ||||||
|         set |  | ||||||
|         { |  | ||||||
|             if (value != null) |  | ||||||
|                 _value = value?.ToString()?.GetJTokenFromString(); |  | ||||||
|             else |  | ||||||
|                 _value = null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     private object? _value; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 保存初始值 |  | ||||||
|     /// </summary> |  | ||||||
|     public virtual bool SaveValue { get; set; } = false; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 变量额外属性Json |  | ||||||
|     /// </summary> |  | ||||||
|     public Dictionary<long, Dictionary<string, string>>? VariablePropertys { get; set; } |  | ||||||
|  |  | ||||||
|     #region 报警 |  | ||||||
|     /// <summary> |  | ||||||
|     /// 报警延时 |  | ||||||
|     /// </summary> |  | ||||||
|     public int AlarmDelay { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔开报警使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public bool BoolOpenAlarmEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔开报警约束 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? BoolOpenRestrainExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔开报警文本 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? BoolOpenAlarmText { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔关报警使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public bool BoolCloseAlarmEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔关报警约束 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? BoolCloseRestrainExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔关报警文本 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? BoolCloseAlarmText { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高报使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public bool HAlarmEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高报约束 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? HRestrainExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高报文本 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? HAlarmText { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高限值 |  | ||||||
|     /// </summary> |  | ||||||
|     public double? HAlarmCode { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高高报使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public bool HHAlarmEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高高报约束 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? HHRestrainExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高高报文本 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? HHAlarmText { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高高限值 |  | ||||||
|     /// </summary> |  | ||||||
|     public double? HHAlarmCode { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低报使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public bool LAlarmEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低报约束 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? LRestrainExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低报文本 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? LAlarmText { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低限值 |  | ||||||
|     /// </summary> |  | ||||||
|     public double? LAlarmCode { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低低报使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public bool LLAlarmEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低低报约束 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? LLRestrainExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低低报文本 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? LLAlarmText { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低低限值 |  | ||||||
|     /// </summary> |  | ||||||
|     public double? LLAlarmCode { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义报警使能 |  | ||||||
|     /// </summary> |  | ||||||
|     public bool CustomAlarmEnable { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义报警条件约束 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? CustomRestrainExpressions { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义文本 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? CustomAlarmText { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义报警条件 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? CustomAlarmCode { get; set; } |  | ||||||
|  |  | ||||||
|     #endregion 报警 |  | ||||||
|  |  | ||||||
|     #region 备用字段 |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark1 { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark2 { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark3 { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark4 { get; set; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义 |  | ||||||
|     /// </summary> |  | ||||||
|     public string? Remark5 { get; set; } |  | ||||||
|  |  | ||||||
|     #endregion 备用字段 |  | ||||||
|  |  | ||||||
|     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) |  | ||||||
|     { |  | ||||||
|         if (string.IsNullOrEmpty(RegisterAddress) && string.IsNullOrEmpty(OtherMethod)) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(RegisterAddress), nameof(OtherMethod) }); |  | ||||||
|         } |  | ||||||
|         if (HHAlarmEnable && HHAlarmCode == null) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HHAlarmCode cannot be null when HHAlarmEnable is true", new[] { nameof(HHAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (HAlarmEnable && HAlarmCode == null) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HAlarmCode cannot be null when HAlarmEnable is true", new[] { nameof(HAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (LAlarmEnable && LAlarmCode == null) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("LAlarmCode cannot be null when LAlarmEnable is true", new[] { nameof(LAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (LLAlarmEnable && LLAlarmCode == null) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("LLAlarmCode cannot be null when LLAlarmEnable is true", new[] { nameof(LLAlarmCode) }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (HHAlarmEnable && HAlarmEnable && HHAlarmCode <= HAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HHAlarmCode must be greater than HAlarmCode", new[] { nameof(HHAlarmCode), nameof(HAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (HAlarmEnable && LAlarmEnable && HAlarmCode <= LAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HAlarmCode must be greater than LAlarmCode", new[] { nameof(HAlarmCode), nameof(LAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (LAlarmEnable && LLAlarmEnable && LAlarmCode <= LLAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("LAlarmCode must be greater than LLAlarmCode", new[] { nameof(LAlarmCode), nameof(LLAlarmCode) }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (HHAlarmEnable && LAlarmEnable && HHAlarmCode <= LAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HHAlarmCode should be greater than or less than LAlarmCode", new[] { nameof(HHAlarmCode), nameof(LAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (HHAlarmEnable && LLAlarmEnable && HHAlarmCode <= LLAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HHAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HHAlarmCode), nameof(LLAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (HAlarmEnable && LLAlarmEnable && HAlarmCode <= LLAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HAlarmCode), nameof(LLAlarmCode) }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -14,6 +14,8 @@ using Microsoft.AspNetCore.Mvc; | |||||||
| using System.ComponentModel; | using System.ComponentModel; | ||||||
|  |  | ||||||
| using ThingsGateway.NewLife.Extension; | using ThingsGateway.NewLife.Extension; | ||||||
|  |  | ||||||
|  | using TouchSocket.Rpc; | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Gateway.Application; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -25,7 +27,9 @@ namespace ThingsGateway.Gateway.Application; | |||||||
| [ApiController] | [ApiController] | ||||||
| [RolePermission] | [RolePermission] | ||||||
| [Authorize(AuthenticationSchemes = "Bearer")] | [Authorize(AuthenticationSchemes = "Bearer")] | ||||||
| public class RuntimeInfoController : ControllerBase | [TouchSocket.WebApi.Router("/miniapi/[api]/[action]")] | ||||||
|  | [TouchSocket.WebApi.EnableCors("cors")] | ||||||
|  | public class RuntimeInfoController : ControllerBase, IRpcServer | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取冗余状态 |     /// 获取冗余状态 | ||||||
| @@ -33,6 +37,7 @@ public class RuntimeInfoController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpGet("redundancyStatus")] |     [HttpGet("redundancyStatus")] | ||||||
|     [DisplayName("获取冗余状态")] |     [DisplayName("获取冗余状态")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)] | ||||||
|     public bool GetRedundancyStatus() |     public bool GetRedundancyStatus() | ||||||
|     { |     { | ||||||
|         return GlobalData.StartCollectChannelEnable; |         return GlobalData.StartCollectChannelEnable; | ||||||
| @@ -44,7 +49,8 @@ public class RuntimeInfoController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpGet("channelList")] |     [HttpGet("channelList")] | ||||||
|     [DisplayName("获取通道信息")] |     [DisplayName("获取通道信息")] | ||||||
|     public async Task<SqlSugarPagedList<ChannelRuntime>> GetChannelListAsync([FromQuery] ChannelPageInput input) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public async Task<SqlSugarPagedList<ChannelRuntime>> GetChannelListAsync([FromQuery][TouchSocket.WebApi.FromBody] ChannelPageInput input) | ||||||
|     { |     { | ||||||
|         var channelRuntimes = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false); |         var channelRuntimes = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false); | ||||||
|  |  | ||||||
| @@ -62,7 +68,8 @@ public class RuntimeInfoController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpGet("deviceList")] |     [HttpGet("deviceList")] | ||||||
|     [DisplayName("获取设备信息")] |     [DisplayName("获取设备信息")] | ||||||
|     public async Task<SqlSugarPagedList<DeviceRuntime>> GetDeviceListAsync([FromQuery] DevicePageInput input) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public async Task<SqlSugarPagedList<DeviceRuntime>> GetDeviceListAsync([FromQuery][TouchSocket.WebApi.FromBody] DevicePageInput input) | ||||||
|     { |     { | ||||||
|         var deviceRuntimes = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false); |         var deviceRuntimes = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false); | ||||||
|         var data = deviceRuntimes |         var data = deviceRuntimes | ||||||
| @@ -80,9 +87,10 @@ public class RuntimeInfoController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpGet("realAlarmList")] |     [HttpGet("realAlarmList")] | ||||||
|     [DisplayName("获取实时报警变量信息")] |     [DisplayName("获取实时报警变量信息")] | ||||||
|     public async Task<SqlSugarPagedList<AlarmVariable>> GetRealAlarmList([FromQuery] AlarmVariablePageInput input) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public async Task<SqlSugarPagedList<AlarmVariable>> GetRealAlarmList([FromQuery][TouchSocket.WebApi.FromBody] AlarmVariablePageInput input) | ||||||
|     { |     { | ||||||
|         var realAlarmVariables = await GlobalData.GetCurrentUserRealAlarmVariables().ConfigureAwait(false); |         var realAlarmVariables = await GlobalData.GetCurrentUserRealAlarmVariablesAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|         var data = realAlarmVariables |         var data = realAlarmVariables | ||||||
|             .WhereIF(!input.RegisterAddress.IsNullOrEmpty(), a => a.RegisterAddress == input.RegisterAddress) |             .WhereIF(!input.RegisterAddress.IsNullOrEmpty(), a => a.RegisterAddress == input.RegisterAddress) | ||||||
| @@ -98,7 +106,8 @@ public class RuntimeInfoController : ControllerBase | |||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     [HttpGet("variableList")] |     [HttpGet("variableList")] | ||||||
|     [DisplayName("获取变量信息")] |     [DisplayName("获取变量信息")] | ||||||
|     public async Task<SqlSugarPagedList<VariableRuntime>> GetVariableList([FromQuery] VariablePageInput input) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public async Task<SqlSugarPagedList<VariableRuntime>> GetVariableList([FromQuery][TouchSocket.WebApi.FromBody] VariablePageInput input) | ||||||
|     { |     { | ||||||
|         var variables = await GlobalData.GetCurrentUserIdVariables().ConfigureAwait(false); |         var variables = await GlobalData.GetCurrentUserIdVariables().ConfigureAwait(false); | ||||||
|         var data = variables |         var data = variables | ||||||
| @@ -117,6 +126,7 @@ public class RuntimeInfoController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpGet("getPluginPropertys")] |     [HttpGet("getPluginPropertys")] | ||||||
|     [DisplayName("获取默认插件属性")] |     [DisplayName("获取默认插件属性")] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)] | ||||||
|     public Dictionary<string, string> GetPluginPropertys(string pluginName) |     public Dictionary<string, string> GetPluginPropertys(string pluginName) | ||||||
|     { |     { | ||||||
|         var data = GlobalData.PluginService.GetDriverPropertyTypes(pluginName); |         var data = GlobalData.PluginService.GetDriverPropertyTypes(pluginName); | ||||||
| @@ -131,10 +141,11 @@ public class RuntimeInfoController : ControllerBase | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [HttpGet("getPluginInfos")] |     [HttpGet("getPluginInfos")] | ||||||
|     [DisplayName("获取插件")] |     [DisplayName("获取插件")] | ||||||
|     public SqlSugarPagedList<PluginInfo> GetPluginInfos([FromQuery] PluginInfoPageInput input) |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||||
|  |     public SqlSugarPagedList<PluginInfo> GetPluginInfos([FromQuery][TouchSocket.WebApi.FromBody] PluginInfoPageInput input) | ||||||
|     { |     { | ||||||
|         //指定关键词搜索为插件FullName |         //指定关键词搜索为插件FullName | ||||||
|         return GlobalData.PluginService.GetList().WhereIF(!input.Name.IsNullOrWhiteSpace(), a => a.Name == input.Name) |         return (GlobalData.PluginService.GetPluginList()).WhereIF(!input.Name.IsNullOrWhiteSpace(), a => a.Name == input.Name) | ||||||
|                 .ToPagedList(input); |                 .ToPagedList(input); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,14 +11,19 @@ | |||||||
| using Microsoft.AspNetCore.Authorization; | using Microsoft.AspNetCore.Authorization; | ||||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Admin.Application; | using TouchSocket.Rpc; | ||||||
|  | 
 | ||||||
|  | namespace ThingsGateway.Gateway.Application; | ||||||
| 
 | 
 | ||||||
| [Route("api/[controller]/[action]")]
 | [Route("api/[controller]/[action]")]
 | ||||||
| [AllowAnonymous] | [AllowAnonymous] | ||||||
| [ApiController] | [ApiController] | ||||||
| public class TestController : ControllerBase | [TouchSocket.WebApi.Router("/miniapi/[api]/[action]")]
 | ||||||
|  | [TouchSocket.WebApi.EnableCors("cors")] | ||||||
|  | public class TestController : ControllerBase, IRpcServer | ||||||
| { | { | ||||||
|     [HttpGet] |     [HttpGet] | ||||||
|  |     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)] | ||||||
|     public void Test() |     public void Test() | ||||||
|     { |     { | ||||||
|         GC.Collect(); |         GC.Collect(); | ||||||
| @@ -59,10 +59,10 @@ public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache | |||||||
|  |  | ||||||
|         await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false); |         await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|     protected override void Dispose(bool disposing) |     protected override Task DisposeAsync(bool disposing) | ||||||
|     { |     { | ||||||
|         GlobalData.AlarmChangedEvent -= AlarmValueChange; |         GlobalData.AlarmChangedEvent -= AlarmValueChange; | ||||||
|         base.Dispose(disposing); |         return base.DisposeAsync(disposing); | ||||||
|     } |     } | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。 |     /// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。 | ||||||
|   | |||||||
| @@ -130,7 +130,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 释放资源方法 |     /// 释放资源方法 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     protected override void Dispose(bool disposing) |     protected override Task DisposeAsync(bool disposing) | ||||||
|     { |     { | ||||||
|         // 解绑事件 |         // 解绑事件 | ||||||
|         GlobalData.AlarmChangedEvent -= AlarmValueChange; |         GlobalData.AlarmChangedEvent -= AlarmValueChange; | ||||||
| @@ -142,7 +142,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | |||||||
|         _memoryDevModelQueue.Clear(); |         _memoryDevModelQueue.Clear(); | ||||||
|         _memoryVarModelQueue.Clear(); |         _memoryVarModelQueue.Clear(); | ||||||
|         _memoryVarModelsQueue.Clear(); |         _memoryVarModelsQueue.Clear(); | ||||||
|         base.Dispose(disposing); |         return base.DisposeAsync(disposing); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -21,6 +21,8 @@ public interface IDBHistoryAlarm | |||||||
|     string? Description { get; set; } |     string? Description { get; set; } | ||||||
|     string DeviceName { get; set; } |     string DeviceName { get; set; } | ||||||
|     DateTime EventTime { get; set; } |     DateTime EventTime { get; set; } | ||||||
|  |     DateTime FinishTime { get; set; } | ||||||
|  |     DateTime ConfirmTime { get; set; } | ||||||
|     EventTypeEnum EventType { get; set; } |     EventTypeEnum EventType { get; set; } | ||||||
|     string Name { get; set; } |     string Name { get; set; } | ||||||
|     string RegisterAddress { get; set; } |     string RegisterAddress { get; set; } | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ public class CacheDBItem<T> : IPrimaryIdEntity | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     [SugarColumn(IsPrimaryKey = true)] |     [SugarColumn(IsPrimaryKey = true)] | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public long Id { get; set; } |     public long Id { get; set; } | ||||||
|  |  | ||||||
|     [SugarColumn(IsJson = true, ColumnDataType = "TEXT")] |     [SugarColumn(IsJson = true, ColumnDataType = "TEXT")] | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ namespace ThingsGateway.Gateway.Application; | |||||||
| /// 采集插件,继承实现不同PLC通讯 | /// 采集插件,继承实现不同PLC通讯 | ||||||
| /// <para></para> | /// <para></para> | ||||||
| /// </summary> | /// </summary> | ||||||
| public abstract class CollectBase : DriverBase, IRpcDriver | public abstract partial class CollectBase : DriverBase, IRpcDriver | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 插件配置项 |     /// 插件配置项 | ||||||
| @@ -278,11 +278,9 @@ public abstract class CollectBase : DriverBase, IRpcDriver | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #region private |  | ||||||
|  |  | ||||||
|     #region 执行方法 |     #region 执行方法 | ||||||
|  |     async ValueTask ReadVariableMed(object? state, CancellationToken cancellationToken) | ||||||
|     async Task ReadVariableMed(object? state, CancellationToken cancellationToken) |  | ||||||
|     { |     { | ||||||
|         if (state is not VariableMethod readVariableMethods) return; |         if (state is not VariableMethod readVariableMethods) return; | ||||||
|         if (Pause) |         if (Pause) | ||||||
| @@ -348,33 +346,33 @@ public abstract class CollectBase : DriverBase, IRpcDriver | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     #endregion |     #endregion | ||||||
|  |     private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new(); | ||||||
|  |  | ||||||
|     #region 执行默认读取 |     #region 执行默认读取 | ||||||
|  |     async ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken) | ||||||
|     async Task ReadVariableSource(object? state, CancellationToken cancellationToken) |  | ||||||
|     { |     { | ||||||
|  |  | ||||||
|         if (state is not VariableSourceRead variableSourceRead) return; |         if (state is not VariableSourceRead variableSourceRead) return; | ||||||
|  |  | ||||||
|         if (Pause) return; |         if (Pause) return; | ||||||
|         if (cancellationToken.IsCancellationRequested) return; |         if (cancellationToken.IsCancellationRequested) return; | ||||||
|  |  | ||||||
|         var readErrorCount = 0; |  | ||||||
|  |  | ||||||
|         var readToken = await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false); |         var readToken = await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         if (readToken.IsCancellationRequested) |         if (readToken.IsCancellationRequested) | ||||||
|         { |         { | ||||||
|             await ReadVariableSource(state, cancellationToken).ConfigureAwait(false); |             await ReadVariableSource(state, cancellationToken).ConfigureAwait(false); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         using var allTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, readToken); |         var allTokenSource = _linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken); | ||||||
|         var allToken = allTokenSource.Token; |         var allToken = allTokenSource.Token; | ||||||
|  |  | ||||||
|         //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |         //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|         //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length)); |         //    LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length)); | ||||||
|         var readResult = await ReadSourceAsync(variableSourceRead, allToken).ConfigureAwait(false); |         var readResult = await ReadSourceAsync(variableSourceRead, allToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         var readErrorCount = 0; | ||||||
|  |  | ||||||
|         // 读取失败时重试一定次数 |         // 读取失败时重试一定次数 | ||||||
|         while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount) |         while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount) | ||||||
|         { |         { | ||||||
| @@ -434,13 +432,15 @@ public abstract class CollectBase : DriverBase, IRpcDriver | |||||||
|             variableSourceRead.LastErrorMessage = readResult.ErrorMessage; |             variableSourceRead.LastErrorMessage = readResult.ErrorMessage; | ||||||
|             CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage); |             CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage); | ||||||
|             var time = DateTime.Now; |             var time = DateTime.Now; | ||||||
|             variableSourceRead.VariableRuntimes.ForEach(a => a.SetValue(null, time, isOnline: false)); |             foreach (var item in variableSourceRead.VariableRuntimes) | ||||||
|  |             { | ||||||
|  |                 item.SetValue(null, time, isOnline: false); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|     #endregion |  | ||||||
|  |  | ||||||
|     protected virtual Task TestOnline(object? state, CancellationToken cancellationToken) |     protected virtual Task TestOnline(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
| @@ -688,19 +688,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver | |||||||
|             { |             { | ||||||
|                 // 调用方法并获取结果 |                 // 调用方法并获取结果 | ||||||
|                 var data = await variableMethod.InvokeMethodAsync(this, value, cancellationToken).ConfigureAwait(false); |                 var data = await variableMethod.InvokeMethodAsync(this, value, cancellationToken).ConfigureAwait(false); | ||||||
|                 result = new(data); |  | ||||||
|                 var operResultType = typeof(IOperResult<>); |  | ||||||
|                 var interfaceType = data.GetType().GetInterfaces() |  | ||||||
|                     .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == operResultType); |  | ||||||
|  |  | ||||||
|                 if (interfaceType != null) |                 result = data.GetOperResult(); | ||||||
|                 { |  | ||||||
|                     var contentProperty = interfaceType.GetProperty("Content"); |  | ||||||
|                     if (contentProperty != null) |  | ||||||
|                     { |  | ||||||
|                         result.Content = contentProperty.GetValue(data); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // 如果方法有返回值,并且是读取操作 |                 // 如果方法有返回值,并且是读取操作 | ||||||
|                 if (method.HasReturn && isRead) |                 if (method.HasReturn && isRead) | ||||||
| @@ -731,5 +720,12 @@ public abstract class CollectBase : DriverBase, IRpcDriver | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     #endregion 写入方法 |     #endregion 写入方法 | ||||||
|  |  | ||||||
|  |     protected override Task DisposeAsync(bool disposing) | ||||||
|  |     { | ||||||
|  |         _linkedCtsCache?.SafeDispose(); | ||||||
|  |         return base.DisposeAsync(disposing); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -47,10 +47,11 @@ public abstract class CollectFoundationBase : CollectBase | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override void Dispose(bool disposing) |     protected override async Task DisposeAsync(bool disposing) | ||||||
|     { |     { | ||||||
|         FoundationDevice?.Dispose(); |         if (FoundationDevice != null) | ||||||
|         base.Dispose(disposing); |             await FoundationDevice.SafeDisposeAsync().ConfigureAwait(false); | ||||||
|  |         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 开始通讯执行的方法 |     /// 开始通讯执行的方法 | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ public abstract class CollectPropertyBase : DriverPropertyBase | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 读写占空比 |     /// 读写占空比 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|  |     [MinValue(1)] | ||||||
|     public virtual int DutyCycle { get; set; } = 3; |     public virtual int DutyCycle { get; set; } = 3; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -52,6 +53,7 @@ public abstract class CollectPropertyRetryBase : CollectPropertyBase | |||||||
|     public override int RetryCount { get; set; } = 3; |     public override int RetryCount { get; set; } = 3; | ||||||
|  |  | ||||||
|     [DynamicProperty(Remark = "n 次写入操作会执行一次读取")] |     [DynamicProperty(Remark = "n 次写入操作会执行一次读取")] | ||||||
|  |     [MinValue(1)] | ||||||
|     public override int DutyCycle { get; set; } = 3; |     public override int DutyCycle { get; set; } = 3; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -26,11 +26,12 @@ namespace ThingsGateway.Gateway.Application; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// 插件基类 | /// 插件基类 | ||||||
| /// </summary> | /// </summary> | ||||||
| public abstract class DriverBase : DisposableObject, IDriver | public abstract class DriverBase : AsyncDisposableObject, IDriver | ||||||
| { | { | ||||||
|     /// <inheritdoc cref="DriverBase"/> |     /// <inheritdoc cref="DriverBase"/> | ||||||
|     public DriverBase() |     public DriverBase() | ||||||
|     { |     { | ||||||
|  |  | ||||||
|         Localizer = App.CreateLocalizerByType(typeof(DriverBase))!; |         Localizer = App.CreateLocalizerByType(typeof(DriverBase))!; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -39,8 +40,7 @@ public abstract class DriverBase : DisposableObject, IDriver | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 当前设备 |     /// 当前设备 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public DeviceRuntime? CurrentDevice => WeakReferenceCurrentDevice?.TryGetTarget(out var target) == true ? target : null; |     public DeviceRuntime? CurrentDevice { get; private set; } | ||||||
|     private WeakReference<DeviceRuntime> WeakReferenceCurrentDevice { get; set; } |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 当前设备Id |     /// 当前设备Id | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -208,7 +208,7 @@ public abstract class DriverBase : DisposableObject, IDriver | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal void InitDevice(DeviceRuntime device) |     internal void InitDevice(DeviceRuntime device) | ||||||
|     { |     { | ||||||
|         WeakReferenceCurrentDevice = new WeakReference<DeviceRuntime>(device); |         CurrentDevice = device; | ||||||
|  |  | ||||||
|         _logger = App.RootServices.GetService<Microsoft.Extensions.Logging.ILoggerFactory>().CreateLogger($"Driver[{CurrentDevice.Name}]"); |         _logger = App.RootServices.GetService<Microsoft.Extensions.Logging.ILoggerFactory>().CreateLogger($"Driver[{CurrentDevice.Name}]"); | ||||||
|  |  | ||||||
| @@ -313,38 +313,45 @@ public abstract class DriverBase : DisposableObject, IDriver | |||||||
|  |  | ||||||
|     protected abstract List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken); |     protected abstract List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken); | ||||||
|  |  | ||||||
|     protected object stopLock = new(); |     protected WaitLock stopLock = new(nameof(DriverBase)); | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 已停止任务,释放插件 |     /// 已停止任务,释放插件 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal virtual void Stop() |     internal virtual async Task StopAsync() | ||||||
|     { |     { | ||||||
|         if (!DisposedValue) |         if (!DisposedValue) | ||||||
|         { |         { | ||||||
|             lock (stopLock) |             await stopLock.WaitAsync().ConfigureAwait(false); | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|  |  | ||||||
|  |  | ||||||
|                 if (!DisposedValue) |                 if (!DisposedValue) | ||||||
|                 { |                 { | ||||||
|                     try |  | ||||||
|                     { |  | ||||||
|                         // 执行资源释放操作 |  | ||||||
|                         Dispose(); |  | ||||||
|                     } |  | ||||||
|                     catch (Exception ex) |  | ||||||
|                     { |  | ||||||
|                         // 记录 Dispose 方法执行失败的错误信息 |  | ||||||
|                         LogMessage?.LogError(ex, "Dispose"); |  | ||||||
|                     } |  | ||||||
|                     // 记录设备线程已停止的信息 |                     // 记录设备线程已停止的信息 | ||||||
|                     LogMessage?.LogInformation(string.Format(AppResource.DeviceTaskStop, DeviceName)); |                     LogMessage?.LogInformation(string.Format(AppResource.DeviceTaskStop, DeviceName)); | ||||||
|  |  | ||||||
|  |                     // 执行资源释放操作 | ||||||
|  |                     await this.SafeDisposeAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 // 记录 Dispose 方法执行失败的错误信息 | ||||||
|  |                 LogMessage?.LogError(ex, "Dispose"); | ||||||
|  |             } | ||||||
|  |             finally | ||||||
|  |             { | ||||||
|  |                 stopLock.Release(); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override void Dispose(bool disposing) |     protected override async Task DisposeAsync(bool disposing) | ||||||
|     { |     { | ||||||
|         base.Dispose(disposing); |         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||||
|         if (TaskSchedulerLoop != null) |         if (TaskSchedulerLoop != null) | ||||||
|         { |         { | ||||||
|             lock (TaskSchedulerLoop) |             lock (TaskSchedulerLoop) | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ using TouchSocket.Core; | |||||||
|  |  | ||||||
| namespace ThingsGateway.Gateway.Application | namespace ThingsGateway.Gateway.Application | ||||||
| { | { | ||||||
|     public interface IDriver : IDisposable |     public interface IDriver : IAsyncDisposable | ||||||
|     { |     { | ||||||
|         bool DisposedValue { get; } |         bool DisposedValue { get; } | ||||||
|         ChannelRuntime CurrentChannel { get; } |         ChannelRuntime CurrentChannel { get; } | ||||||
|   | |||||||
| @@ -0,0 +1,284 @@ | |||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
|  | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
|  | //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||||
|  | //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||||
|  | //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||||
|  | //  使用文档:https://thingsgateway.cn/ | ||||||
|  | //  QQ群:605534569 | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using BootstrapBlazor.Components; | ||||||
|  |  | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Gateway.Application; | ||||||
|  |  | ||||||
|  | public class AlarmPropertys : IValidatableObject | ||||||
|  | { | ||||||
|  |     private int alarmDelay; | ||||||
|  |     private int alarmLevel; | ||||||
|  |  | ||||||
|  |     private decimal hAlarmCode = 50; | ||||||
|  |     private decimal lAlarmCode = 10; | ||||||
|  |     private decimal hHAlarmCode = 90; | ||||||
|  |     private decimal lLAlarmCode = 0; | ||||||
|  |  | ||||||
|  |     private bool boolOpenAlarmEnable; | ||||||
|  |     private bool boolCloseAlarmEnable; | ||||||
|  |     private bool hAlarmEnable; | ||||||
|  |     private bool hHAlarmEnable; | ||||||
|  |     private bool lLAlarmEnable; | ||||||
|  |     private bool lAlarmEnable; | ||||||
|  |     private bool customAlarmEnable; | ||||||
|  |  | ||||||
|  |     private string boolOpenRestrainExpressions; | ||||||
|  |     private string boolOpenAlarmText; | ||||||
|  |     private string boolCloseRestrainExpressions; | ||||||
|  |     private string boolCloseAlarmText; | ||||||
|  |     private string hRestrainExpressions; | ||||||
|  |     private string hAlarmText; | ||||||
|  |     private string hHRestrainExpressions; | ||||||
|  |     private string hHAlarmText; | ||||||
|  |     private string lRestrainExpressions; | ||||||
|  |     private string lAlarmText; | ||||||
|  |  | ||||||
|  |     private string lLRestrainExpressions; | ||||||
|  |     private string lLAlarmText; | ||||||
|  |     private string customRestrainExpressions; | ||||||
|  |     private string customAlarmText; | ||||||
|  |     private string customAlarmCode; | ||||||
|  |  | ||||||
|  |     #region 报警 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 报警等级 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "报警等级")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public int AlarmLevel { get => alarmLevel; set => alarmLevel = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 报警延时 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "报警延时")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public int AlarmDelay { get => alarmDelay; set => alarmDelay = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 布尔开报警使能 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "布尔开报警使能")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public bool BoolOpenAlarmEnable { get => boolOpenAlarmEnable; set => boolOpenAlarmEnable = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 布尔开报警约束 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "布尔开报警约束", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string BoolOpenRestrainExpressions { get => boolOpenRestrainExpressions; set => boolOpenRestrainExpressions = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 布尔开报警文本 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "布尔开报警文本", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string BoolOpenAlarmText { get => boolOpenAlarmText; set => boolOpenAlarmText = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 布尔关报警使能 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "布尔关报警使能")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public bool BoolCloseAlarmEnable { get => boolCloseAlarmEnable; set => boolCloseAlarmEnable = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 布尔关报警约束 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "布尔关报警约束", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string BoolCloseRestrainExpressions { get => boolCloseRestrainExpressions; set => boolCloseRestrainExpressions = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 布尔关报警文本 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "布尔关报警文本", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string BoolCloseAlarmText { get => boolCloseAlarmText; set => boolCloseAlarmText = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 高报使能 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "高报使能")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public bool HAlarmEnable { get => hAlarmEnable; set => hAlarmEnable = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 高报约束 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "高报约束", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string HRestrainExpressions { get => hRestrainExpressions; set => hRestrainExpressions = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 高报文本 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "高报文本", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string HAlarmText { get => hAlarmText; set => hAlarmText = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 高限值 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "高限值", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public decimal HAlarmCode { get => hAlarmCode; set => hAlarmCode = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 高高报使能 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "高高报使能")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public bool HHAlarmEnable { get => hHAlarmEnable; set => hHAlarmEnable = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 高高报约束 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "高高报约束", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string HHRestrainExpressions { get => hHRestrainExpressions; set => hHRestrainExpressions = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 高高报文本 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "高高报文本", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string HHAlarmText { get => hHAlarmText; set => hHAlarmText = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 高高限值 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "高高限值", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public decimal HHAlarmCode { get => hHAlarmCode; set => hHAlarmCode = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 低报使能 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "低报使能")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public bool LAlarmEnable { get => lAlarmEnable; set => lAlarmEnable = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 低报约束 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "低报约束", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string LRestrainExpressions { get => lRestrainExpressions; set => lRestrainExpressions = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 低报文本 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "低报文本", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string LAlarmText { get => lAlarmText; set => lAlarmText = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 低限值 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "低限值", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public decimal LAlarmCode { get => lAlarmCode; set => lAlarmCode = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 低低报使能 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "低低报使能")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public bool LLAlarmEnable { get => lLAlarmEnable; set => lLAlarmEnable = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 低低报约束 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "低低报约束", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string LLRestrainExpressions { get => lLRestrainExpressions; set => lLRestrainExpressions = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 低低报文本 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "低低报文本", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string LLAlarmText { get => lLAlarmText; set => lLAlarmText = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 低低限值 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "低低限值", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public decimal LLAlarmCode { get => lLAlarmCode; set => lLAlarmCode = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义报警使能 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "自定义报警使能")] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public bool CustomAlarmEnable { get => customAlarmEnable; set => customAlarmEnable = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义报警条件约束 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "自定义报警条件约束", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string CustomRestrainExpressions { get => customRestrainExpressions; set => customRestrainExpressions = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义文本 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "自定义文本", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string CustomAlarmText { get => customAlarmText; set => customAlarmText = value; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 自定义报警条件 | ||||||
|  |     /// </summary> | ||||||
|  |     [SugarColumn(ColumnDescription = "自定义报警条件", IsNullable = true)] | ||||||
|  |     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] | ||||||
|  |     public string CustomAlarmCode { get => customAlarmCode; set => customAlarmCode = value; } | ||||||
|  |  | ||||||
|  |     #endregion 报警 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         if (HHAlarmEnable && HAlarmEnable && HHAlarmCode <= HAlarmCode) | ||||||
|  |         { | ||||||
|  |             yield return new ValidationResult("HHAlarmCode must be greater than HAlarmCode", new[] { nameof(HHAlarmCode), nameof(HAlarmCode) }); | ||||||
|  |         } | ||||||
|  |         if (HAlarmEnable && LAlarmEnable && HAlarmCode <= LAlarmCode) | ||||||
|  |         { | ||||||
|  |             yield return new ValidationResult("HAlarmCode must be greater than LAlarmCode", new[] { nameof(HAlarmCode), nameof(LAlarmCode) }); | ||||||
|  |         } | ||||||
|  |         if (LAlarmEnable && LLAlarmEnable && LAlarmCode <= LLAlarmCode) | ||||||
|  |         { | ||||||
|  |             yield return new ValidationResult("LAlarmCode must be greater than LLAlarmCode", new[] { nameof(LAlarmCode), nameof(LLAlarmCode) }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (HHAlarmEnable && LAlarmEnable && HHAlarmCode <= LAlarmCode) | ||||||
|  |         { | ||||||
|  |             yield return new ValidationResult("HHAlarmCode should be greater than or less than LAlarmCode", new[] { nameof(HHAlarmCode), nameof(LAlarmCode) }); | ||||||
|  |         } | ||||||
|  |         if (HHAlarmEnable && LLAlarmEnable && HHAlarmCode <= LLAlarmCode) | ||||||
|  |         { | ||||||
|  |             yield return new ValidationResult("HHAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HHAlarmCode), nameof(LLAlarmCode) }); | ||||||
|  |         } | ||||||
|  |         if (HAlarmEnable && LLAlarmEnable && HAlarmCode <= LLAlarmCode) | ||||||
|  |         { | ||||||
|  |             yield return new ValidationResult("HAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HAlarmCode), nameof(LLAlarmCode) }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -17,8 +17,10 @@ namespace ThingsGateway.Gateway.Application; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// 后台日志表 | /// 后台日志表 | ||||||
| ///</summary> | ///</summary> | ||||||
|  | #if !Management | ||||||
| [SugarTable("backend_log", TableDescription = "后台日志表")] | [SugarTable("backend_log", TableDescription = "后台日志表")] | ||||||
| [Tenant(SqlSugarConst.DB_Log)] | [Tenant(SqlSugarConst.DB_Log)] | ||||||
|  | #endif | ||||||
| public class BackendLog : PrimaryIdEntity | public class BackendLog : PrimaryIdEntity | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -17,13 +17,16 @@ using TouchSocket.Core; | |||||||
| using TouchSocket.Sockets; | using TouchSocket.Sockets; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Gateway.Application; | ||||||
|  | #pragma warning disable CS0649 | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 通道表 | /// 通道表 | ||||||
| /// </summary> | /// </summary> | ||||||
|  | #if !Management | ||||||
| [SugarTable("channel", TableDescription = "通道表")] | [SugarTable("channel", TableDescription = "通道表")] | ||||||
| [Tenant(SqlSugarConst.DB_Custom)] | [Tenant(SqlSugarConst.DB_Custom)] | ||||||
| [SugarIndex("unique_channel_name", nameof(Channel.Name), OrderByType.Asc, true)] | [SugarIndex("unique_channel_name", nameof(Channel.Name), OrderByType.Asc, true)] | ||||||
|  | #endif | ||||||
| public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IBaseEntity | public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IBaseEntity | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -32,6 +35,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB | |||||||
|     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] |     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] | ||||||
|     [IgnoreExcel] |     [IgnoreExcel] | ||||||
|     [AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] |     [AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public virtual long Id { get; set; } |     public virtual long Id { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -17,13 +17,16 @@ using System.ComponentModel.DataAnnotations; | |||||||
| using ThingsGateway.NewLife.Extension; | using ThingsGateway.NewLife.Extension; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Gateway.Application; | ||||||
|  | #pragma warning disable CS0649 | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 设备表 | /// 设备表 | ||||||
| /// </summary> | /// </summary> | ||||||
|  | #if !Management | ||||||
| [SugarTable("device", TableDescription = "设备表")] | [SugarTable("device", TableDescription = "设备表")] | ||||||
| [Tenant(SqlSugarConst.DB_Custom)] | [Tenant(SqlSugarConst.DB_Custom)] | ||||||
| [SugarIndex("unique_device_name", nameof(Device.Name), OrderByType.Asc, true)] | [SugarIndex("unique_device_name", nameof(Device.Name), OrderByType.Asc, true)] | ||||||
|  | #endif | ||||||
| public class Device : BaseDataEntity, IValidatableObject | public class Device : BaseDataEntity, IValidatableObject | ||||||
| { | { | ||||||
|     public override string ToString() |     public override string ToString() | ||||||
|   | |||||||
| @@ -15,8 +15,10 @@ namespace ThingsGateway.Gateway.Application; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// Rpc写入日志 | /// Rpc写入日志 | ||||||
| ///</summary> | ///</summary> | ||||||
|  | #if !Management | ||||||
| [SugarTable("rpc_log", TableDescription = "RPC操作日志")] | [SugarTable("rpc_log", TableDescription = "RPC操作日志")] | ||||||
| [Tenant(SqlSugarConst.DB_Log)] | [Tenant(SqlSugarConst.DB_Log)] | ||||||
|  | #endif | ||||||
| public class RpcLog : PrimaryIdEntity | public class RpcLog : PrimaryIdEntity | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -16,14 +16,17 @@ using System.Collections.Concurrent; | |||||||
| using System.ComponentModel.DataAnnotations; | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Gateway.Application; | ||||||
|  | #pragma warning disable CS0649 | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| /// 设备变量表 | /// 设备变量表 | ||||||
| /// </summary> | /// </summary> | ||||||
|  | #if !Management | ||||||
| [SugarTable("variable", TableDescription = "设备变量表")] | [SugarTable("variable", TableDescription = "设备变量表")] | ||||||
| [Tenant(SqlSugarConst.DB_Custom)] | [Tenant(SqlSugarConst.DB_Custom)] | ||||||
| [SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)] | [SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)] | ||||||
| [SugarIndex("unique_deviceid_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)] | [SugarIndex("unique_deviceid_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)] | ||||||
|  | #endif | ||||||
| public class Variable : BaseDataEntity, IValidatableObject | public class Variable : BaseDataEntity, IValidatableObject | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -31,6 +34,7 @@ public class Variable : BaseDataEntity, IValidatableObject | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] |     [SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)] | ||||||
|     [AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] |     [AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)] | ||||||
|  |     [System.ComponentModel.DataAnnotations.Key] | ||||||
|     public override long Id { get; set; } |     public override long Id { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -39,13 +43,9 @@ public class Variable : BaseDataEntity, IValidatableObject | |||||||
|     [System.Text.Json.Serialization.JsonIgnore] |     [System.Text.Json.Serialization.JsonIgnore] | ||||||
|     [Newtonsoft.Json.JsonIgnore] |     [Newtonsoft.Json.JsonIgnore] | ||||||
|     internal long Row; |     internal long Row; | ||||||
|     private double hAlarmCode = 50; |  | ||||||
|     private double lAlarmCode = 10; |  | ||||||
|     private double hHAlarmCode = 90; |  | ||||||
|     private double lLAlarmCode = 0; |  | ||||||
|     private long deviceId; |     private long deviceId; | ||||||
|     private int? arrayLength; |     private int? arrayLength; | ||||||
|     private int alarmDelay; |  | ||||||
|     private ProtectTypeEnum protectType = ProtectTypeEnum.ReadWrite; |     private ProtectTypeEnum protectType = ProtectTypeEnum.ReadWrite; | ||||||
|     private DataTypeEnum dataType = DataTypeEnum.Int16; |     private DataTypeEnum dataType = DataTypeEnum.Int16; | ||||||
|  |  | ||||||
| @@ -59,13 +59,6 @@ public class Variable : BaseDataEntity, IValidatableObject | |||||||
|     public bool DynamicVariable; |     public bool DynamicVariable; | ||||||
|     private bool rpcWriteEnable = true; |     private bool rpcWriteEnable = true; | ||||||
|     private bool saveValue = false; |     private bool saveValue = false; | ||||||
|     private bool boolOpenAlarmEnable; |  | ||||||
|     private bool boolCloseAlarmEnable; |  | ||||||
|     private bool hAlarmEnable; |  | ||||||
|     private bool hHAlarmEnable; |  | ||||||
|     private bool lLAlarmEnable; |  | ||||||
|     private bool lAlarmEnable; |  | ||||||
|     private bool customAlarmEnable; |  | ||||||
|     private bool businessGroupUpdateTrigger = true; |     private bool businessGroupUpdateTrigger = true; | ||||||
|     private bool rpcWriteCheck; |     private bool rpcWriteCheck; | ||||||
|  |  | ||||||
| @@ -80,29 +73,17 @@ public class Variable : BaseDataEntity, IValidatableObject | |||||||
|     private string otherMethod; |     private string otherMethod; | ||||||
|     private string readExpressions; |     private string readExpressions; | ||||||
|     private string writeExpressions; |     private string writeExpressions; | ||||||
|     private string boolOpenRestrainExpressions; |  | ||||||
|     private string boolOpenAlarmText; |  | ||||||
|     private string boolCloseRestrainExpressions; |  | ||||||
|     private string boolCloseAlarmText; |  | ||||||
|     private string hRestrainExpressions; |  | ||||||
|     private string hAlarmText; |  | ||||||
|     private Dictionary<long, Dictionary<string, string>>? variablePropertys; |  | ||||||
|     private string hHRestrainExpressions; |  | ||||||
|     private string hHAlarmText; |  | ||||||
|     private string lRestrainExpressions; |  | ||||||
|     private string lAlarmText; |  | ||||||
|  |  | ||||||
|     private string lLRestrainExpressions; |     private Dictionary<long, Dictionary<string, string>>? variablePropertys; | ||||||
|     private string lLAlarmText; |  | ||||||
|     private string customRestrainExpressions; |  | ||||||
|     private string customAlarmText; |  | ||||||
|     private string customAlarmCode; |  | ||||||
|     private string remark1; |     private string remark1; | ||||||
|     private string remark2; |     private string remark2; | ||||||
|     private string remark3; |     private string remark3; | ||||||
|     private string remark4; |     private string remark4; | ||||||
|     private string remark5; |     private string remark5; | ||||||
|  |  | ||||||
|  |     [MapperIgnore] | ||||||
|  |     public ValidateForm AlarmPropertysValidateForm; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 变量额外属性Json |     /// 变量额外属性Json | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -271,197 +252,15 @@ public class Variable : BaseDataEntity, IValidatableObject | |||||||
|     [AutoGenerateColumn(Ignore = true)] |     [AutoGenerateColumn(Ignore = true)] | ||||||
|     public Dictionary<long, Dictionary<string, string>>? VariablePropertys { get => variablePropertys; set => variablePropertys = value; } |     public Dictionary<long, Dictionary<string, string>>? VariablePropertys { get => variablePropertys; set => variablePropertys = value; } | ||||||
|  |  | ||||||
|     #region 报警 |  | ||||||
|     /// <summary> |  | ||||||
|     /// 报警延时 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "报警延时")] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public int AlarmDelay { get => alarmDelay; set => alarmDelay = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 布尔开报警使能 |     /// 变量报警属性Json | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     [SugarColumn(ColumnDescription = "布尔开报警使能")] |     [SugarColumn(IsJson = true, ColumnDataType = StaticConfig.CodeFirst_BigString, ColumnDescription = "报警属性Json", IsNullable = true)] | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |     [IgnoreExcel] | ||||||
|     public bool BoolOpenAlarmEnable { get => boolOpenAlarmEnable; set => boolOpenAlarmEnable = value; } |     [AutoGenerateColumn(Ignore = true)] | ||||||
|  |     public AlarmPropertys? AlarmPropertys { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔开报警约束 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "布尔开报警约束", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string BoolOpenRestrainExpressions { get => boolOpenRestrainExpressions; set => boolOpenRestrainExpressions = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔开报警文本 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "布尔开报警文本", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string BoolOpenAlarmText { get => boolOpenAlarmText; set => boolOpenAlarmText = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔关报警使能 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "布尔关报警使能")] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public bool BoolCloseAlarmEnable { get => boolCloseAlarmEnable; set => boolCloseAlarmEnable = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔关报警约束 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "布尔关报警约束", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string BoolCloseRestrainExpressions { get => boolCloseRestrainExpressions; set => boolCloseRestrainExpressions = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 布尔关报警文本 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "布尔关报警文本", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string BoolCloseAlarmText { get => boolCloseAlarmText; set => boolCloseAlarmText = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高报使能 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "高报使能")] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public bool HAlarmEnable { get => hAlarmEnable; set => hAlarmEnable = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高报约束 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "高报约束", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string HRestrainExpressions { get => hRestrainExpressions; set => hRestrainExpressions = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高报文本 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "高报文本", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string HAlarmText { get => hAlarmText; set => hAlarmText = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高限值 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "高限值", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public double HAlarmCode { get => hAlarmCode; set => hAlarmCode = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高高报使能 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "高高报使能")] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public bool HHAlarmEnable { get => hHAlarmEnable; set => hHAlarmEnable = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高高报约束 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "高高报约束", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string HHRestrainExpressions { get => hHRestrainExpressions; set => hHRestrainExpressions = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高高报文本 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "高高报文本", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string HHAlarmText { get => hHAlarmText; set => hHAlarmText = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 高高限值 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "高高限值", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public double HHAlarmCode { get => hHAlarmCode; set => hHAlarmCode = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低报使能 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "低报使能")] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public bool LAlarmEnable { get => lAlarmEnable; set => lAlarmEnable = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低报约束 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "低报约束", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string LRestrainExpressions { get => lRestrainExpressions; set => lRestrainExpressions = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低报文本 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "低报文本", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string LAlarmText { get => lAlarmText; set => lAlarmText = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低限值 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "低限值", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public double LAlarmCode { get => lAlarmCode; set => lAlarmCode = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低低报使能 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "低低报使能")] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public bool LLAlarmEnable { get => lLAlarmEnable; set => lLAlarmEnable = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低低报约束 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "低低报约束", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string LLRestrainExpressions { get => lLRestrainExpressions; set => lLRestrainExpressions = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低低报文本 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "低低报文本", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string LLAlarmText { get => lLAlarmText; set => lLAlarmText = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 低低限值 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "低低限值", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public double LLAlarmCode { get => lLAlarmCode; set => lLAlarmCode = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义报警使能 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "自定义报警使能")] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public bool CustomAlarmEnable { get => customAlarmEnable; set => customAlarmEnable = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义报警条件约束 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "自定义报警条件约束", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string CustomRestrainExpressions { get => customRestrainExpressions; set => customRestrainExpressions = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义文本 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "自定义文本", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string CustomAlarmText { get => customAlarmText; set => customAlarmText = value; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// 自定义报警条件 |  | ||||||
|     /// </summary> |  | ||||||
|     [SugarColumn(ColumnDescription = "自定义报警条件", IsNullable = true)] |  | ||||||
|     [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)] |  | ||||||
|     public string CustomAlarmCode { get => customAlarmCode; set => customAlarmCode = value; } |  | ||||||
|  |  | ||||||
|     #endregion 报警 |  | ||||||
|  |  | ||||||
|     #region 备用字段 |     #region 备用字段 | ||||||
|  |  | ||||||
| @@ -509,30 +308,6 @@ public class Variable : BaseDataEntity, IValidatableObject | |||||||
|             yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(OtherMethod), nameof(RegisterAddress) }); |             yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(OtherMethod), nameof(RegisterAddress) }); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (HHAlarmEnable && HAlarmEnable && HHAlarmCode <= HAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HHAlarmCode must be greater than HAlarmCode", new[] { nameof(HHAlarmCode), nameof(HAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (HAlarmEnable && LAlarmEnable && HAlarmCode <= LAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HAlarmCode must be greater than LAlarmCode", new[] { nameof(HAlarmCode), nameof(LAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (LAlarmEnable && LLAlarmEnable && LAlarmCode <= LLAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("LAlarmCode must be greater than LLAlarmCode", new[] { nameof(LAlarmCode), nameof(LLAlarmCode) }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (HHAlarmEnable && LAlarmEnable && HHAlarmCode <= LAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HHAlarmCode should be greater than or less than LAlarmCode", new[] { nameof(HHAlarmCode), nameof(LAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (HHAlarmEnable && LLAlarmEnable && HHAlarmCode <= LLAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HHAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HHAlarmCode), nameof(LLAlarmCode) }); |  | ||||||
|         } |  | ||||||
|         if (HAlarmEnable && LLAlarmEnable && HAlarmCode <= LLAlarmCode) |  | ||||||
|         { |  | ||||||
|             yield return new ValidationResult("HAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HAlarmCode), nameof(LLAlarmCode) }); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -39,4 +39,16 @@ public enum EventTypeEnum | |||||||
|     /// 准备恢复 |     /// 准备恢复 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     PrepareFinish, |     PrepareFinish, | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 报警确认并恢复 | ||||||
|  |     /// </summary> | ||||||
|  |     ConfirmAndFinish, | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 重启默认 | ||||||
|  |     /// </summary> | ||||||
|  |     Restart, | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ | |||||||
| // QQ群:605534569 | // QQ群:605534569 | ||||||
| // ------------------------------------------------------------------------------ | // ------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using Microsoft.Extensions.DependencyInjection.Extensions; |  | ||||||
| using Microsoft.Extensions.Hosting; | using Microsoft.Extensions.Hosting; | ||||||
|  |  | ||||||
| namespace Microsoft.Extensions.DependencyInjection; | namespace Microsoft.Extensions.DependencyInjection; | ||||||
| @@ -19,30 +18,19 @@ namespace Microsoft.Extensions.DependencyInjection; | |||||||
| [ThingsGateway.DependencyInjection.SuppressSniffer] | [ThingsGateway.DependencyInjection.SuppressSniffer] | ||||||
| public static class ServiceCollectionHostedServiceExtensions | public static class ServiceCollectionHostedServiceExtensions | ||||||
| { | { | ||||||
|     /// <summary> |  | ||||||
|     /// Add an <see cref="IHostedService"/> registration for the given type. |  | ||||||
|     /// </summary> |  | ||||||
|     /// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam> |  | ||||||
|     /// <param name="services">The <see cref="IServiceCollection"/> to register with.</param> |  | ||||||
|     /// <returns>The original <see cref="IServiceCollection"/>.</returns> |  | ||||||
|     public static IServiceCollection AddGatewayHostedService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services) |  | ||||||
|         where THostedService : class, IHostedService |  | ||||||
|     { |  | ||||||
|         services.AddSingleton<THostedService>(); |  | ||||||
|         services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>(seriveProvider => seriveProvider.GetService<THostedService>())); |  | ||||||
|  |  | ||||||
|         return services; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Add an <see cref="IHostedService"/> registration for the given type. |     /// Add an <see cref="IHostedService"/> registration for the given type. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public static IServiceCollection AddGatewayHostedService<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services) |     public static IServiceCollection AddGatewayHostedService<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services) | ||||||
|     where TService : class, IHostedService |     where TService : class | ||||||
|     where THostedService : class, IHostedService, TService |     where THostedService : class, IHostedService, TService | ||||||
|     { |     { | ||||||
|         services.AddSingleton(typeof(TService), typeof(THostedService)); |  | ||||||
|         services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, TService>(seriveProvider => seriveProvider.GetService<TService>())); |         services.AddSingleton<THostedService>(); | ||||||
|  |         services.AddHostedService<THostedService>(a => a.GetService<THostedService>()); | ||||||
|  |         services.AddSingleton<TService>(a => a.GetService<THostedService>()); | ||||||
|  |  | ||||||
|         return services; |         return services; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,58 +0,0 @@ | |||||||
| //------------------------------------------------------------------------------ |  | ||||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 |  | ||||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 |  | ||||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 |  | ||||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway |  | ||||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway |  | ||||||
| //  使用文档:https://thingsgateway.cn/ |  | ||||||
| //  QQ群:605534569 |  | ||||||
| //------------------------------------------------------------------------------ |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.Gateway.Application |  | ||||||
| { |  | ||||||
|     internal static class USheetDataHelpers |  | ||||||
|     { |  | ||||||
|         public static USheetDatas GetUSheetDatas(Dictionary<string, object> data) |  | ||||||
|         { |  | ||||||
|             var uSheetDatas = new USheetDatas(); |  | ||||||
|  |  | ||||||
|             foreach (var a in data) |  | ||||||
|             { |  | ||||||
|                 var value = (a.Value as IEnumerable<Dictionary<string, object>>).ToList(); |  | ||||||
|  |  | ||||||
|                 var uSheetData = new USheetData(); |  | ||||||
|                 uSheetData.id = a.Key; |  | ||||||
|                 uSheetData.name = a.Key; |  | ||||||
|  |  | ||||||
|                 for (int row1 = 0; row1 < value.Count; row1++) |  | ||||||
|                 { |  | ||||||
|                     if (row1 == 0) |  | ||||||
|                     { |  | ||||||
|                         Dictionary<int, USheetCelldata> usheetColldata = new(); |  | ||||||
|                         int col = 0; |  | ||||||
|                         foreach (var colData in value[row1]) |  | ||||||
|                         { |  | ||||||
|                             usheetColldata.Add(col, new USheetCelldata() { v = colData.Key }); |  | ||||||
|                             col++; |  | ||||||
|                         } |  | ||||||
|                         uSheetData.cellData.Add(row1, usheetColldata); |  | ||||||
|                     } |  | ||||||
|                     { |  | ||||||
|                         Dictionary<int, USheetCelldata> usheetColldata = new(); |  | ||||||
|                         int col = 0; |  | ||||||
|                         foreach (var colData in value[row1]) |  | ||||||
|                         { |  | ||||||
|                             usheetColldata.Add(col, new USheetCelldata() { v = colData.Value }); |  | ||||||
|                             col++; |  | ||||||
|                         } |  | ||||||
|                         uSheetData.cellData.Add(row1 + 1, usheetColldata); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 uSheetData.rowCount = uSheetData.cellData.Count + 100; |  | ||||||
|                 uSheetData.columnCount = uSheetData.cellData.FirstOrDefault().Value?.Count ?? 0; |  | ||||||
|                 uSheetDatas.sheets.Add(a.Key, uSheetData); |  | ||||||
|             } |  | ||||||
|             return uSheetDatas; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -83,7 +83,7 @@ public static class GlobalData | |||||||
|           .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); |           .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static async Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariables() |     public static async Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync() | ||||||
|     { |     { | ||||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); |         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||||
|         return RealAlarmIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 |         return RealAlarmIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 | ||||||
| @@ -177,6 +177,15 @@ public static class GlobalData | |||||||
|         } |         } | ||||||
|         return GlobalData.ChannelThreadManage.DeviceThreadManages.TryGetValue(deviceRuntime.ChannelId, out deviceThreadManage); |         return GlobalData.ChannelThreadManage.DeviceThreadManages.TryGetValue(deviceRuntime.ChannelId, out deviceThreadManage); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static IChannelThreadManage GetChannelThreadManage(ChannelRuntime channelRuntime) | ||||||
|  |     { | ||||||
|  |         if (channelRuntime.DeviceThreadManage?.ChannelThreadManage != null) | ||||||
|  |             return channelRuntime.DeviceThreadManage.ChannelThreadManage; | ||||||
|  |         else | ||||||
|  |             return GlobalData.ChannelThreadManage; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public static Dictionary<IDeviceThreadManage, List<DeviceRuntime>> GetDeviceThreadManages(IEnumerable<DeviceRuntime> deviceRuntimes) |     public static Dictionary<IDeviceThreadManage, List<DeviceRuntime>> GetDeviceThreadManages(IEnumerable<DeviceRuntime> deviceRuntimes) | ||||||
|     { |     { | ||||||
|         Dictionary<IDeviceThreadManage, List<DeviceRuntime>> deviceThreadManages = new(); |         Dictionary<IDeviceThreadManage, List<DeviceRuntime>> deviceThreadManages = new(); | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ public class LogJob : IJob | |||||||
|         await DeleteRpcLog(rpcLogDaysdaysAgo, stoppingToken).ConfigureAwait(false); |         await DeleteRpcLog(rpcLogDaysdaysAgo, stoppingToken).ConfigureAwait(false); | ||||||
|         await DeleteBackendLog(backendLogdaysAgo, stoppingToken).ConfigureAwait(false); |         await DeleteBackendLog(backendLogdaysAgo, stoppingToken).ConfigureAwait(false); | ||||||
|         await DeleteTextLog(stoppingToken).ConfigureAwait(false); |         await DeleteTextLog(stoppingToken).ConfigureAwait(false); | ||||||
|         await DeleteLocalDB(stoppingToken).ConfigureAwait(false); |         DeleteLocalDB(stoppingToken); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static async Task DeleteRpcLog(int daysAgo, CancellationToken stoppingToken) |     private static async Task DeleteRpcLog(int daysAgo, CancellationToken stoppingToken) | ||||||
| @@ -49,8 +49,8 @@ public class LogJob : IJob | |||||||
|         //网关通道日志以通道id命名 |         //网关通道日志以通道id命名 | ||||||
|         var channelService = App.RootServices.GetService<IChannelService>(); |         var channelService = App.RootServices.GetService<IChannelService>(); | ||||||
|         var deviceService = App.RootServices.GetService<IDeviceService>(); |         var deviceService = App.RootServices.GetService<IDeviceService>(); | ||||||
|         var channelNames = (await channelService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet(); |         var channelNames = (GlobalData.Channels.Keys).ToHashSet(); | ||||||
|         var deviceNames = (await deviceService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet(); |         var deviceNames = (GlobalData.Devices.Keys).ToHashSet(); | ||||||
|         var channelBaseDir = LoggerExtensions.GetChannelLogBasePath(); |         var channelBaseDir = LoggerExtensions.GetChannelLogBasePath(); | ||||||
|         Directory.CreateDirectory(channelBaseDir); |         Directory.CreateDirectory(channelBaseDir); | ||||||
|         var deviceBaseDir = LoggerExtensions.GetDeviceLogBasePath(); |         var deviceBaseDir = LoggerExtensions.GetDeviceLogBasePath(); | ||||||
| @@ -112,10 +112,9 @@ public class LogJob : IJob | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task DeleteLocalDB(CancellationToken stoppingToken) |     public void DeleteLocalDB(CancellationToken stoppingToken) | ||||||
|     { |     { | ||||||
|         var deviceService = App.RootServices.GetService<IDeviceService>(); |         var data = (GlobalData.Devices.Keys).ToHashSet(); | ||||||
|         var data = (await deviceService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name).ToHashSet(); |  | ||||||
|         var dir = CacheDBUtil.GetCacheFileBasePath(); |         var dir = CacheDBUtil.GetCacheFileBasePath(); | ||||||
|         string[] dirs = Directory.GetDirectories(dir); |         string[] dirs = Directory.GetDirectories(dir); | ||||||
|         foreach (var item in dirs) |         foreach (var item in dirs) | ||||||
|   | |||||||
| @@ -1,6 +1,32 @@ | |||||||
| { | { | ||||||
|  |   "ThingsGateway.Management.Application.ExportString": { | ||||||
|  |  | ||||||
|   "ThingsGateway.Gateway.Application.DefaultDiagram": { |     "ManagementConfigName": "ManagementConfigName" | ||||||
|  |   }, | ||||||
|  |   "ThingsGateway.Management.Application.ManagementConfig": { | ||||||
|  |  | ||||||
|  |     "Name": "Name", | ||||||
|  |     "ServerUri": "ServerUri", | ||||||
|  |     "Enable": "Enable", | ||||||
|  |     "IsServer": "IsServer", | ||||||
|  |     "VerifyToken": "VerifyToken", | ||||||
|  |     "HeartbeatInterval": "HeartbeatInterval", | ||||||
|  |  | ||||||
|  |     "ImportNullError": "Unable to recognize" | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   "ThingsGateway.Management.Application.UpdateZipFile": { | ||||||
|  |     "AppName": "AppName", | ||||||
|  |     "Architecture": "Architecture", | ||||||
|  |     "DotNetVersion": "DotNetVersion", | ||||||
|  |     "FilePath": "FilePath", | ||||||
|  |     "FileSize": "FileSize", | ||||||
|  |     "MinimumCompatibleVersion": "MinimumCompatibleVersion", | ||||||
|  |     "OSPlatform": "OSPlatform", | ||||||
|  |     "Version": "Version" | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   "ThingsGateway.Gateway.Application.INode": { | ||||||
|  |  | ||||||
|     "Actuator": "Actuator", |     "Actuator": "Actuator", | ||||||
|     "AlarmChangedTriggerNode": "AlarmStateTrigger", |     "AlarmChangedTriggerNode": "AlarmStateTrigger", | ||||||
| @@ -53,11 +79,7 @@ | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |  | ||||||
|   "ThingsGateway.Management.AutoUpdateController": { |   "ThingsGateway.Gateway.Application.RedundancyHostedService": { | ||||||
|     "AutoUpdateController": "AutoUpdate", |  | ||||||
|     "Update": "Update" |  | ||||||
|   }, |  | ||||||
|   "ThingsGateway.Management.RedundancyHostedService": { |  | ||||||
|     "ErrorSynchronizingData": "Synchronize data to standby site error", |     "ErrorSynchronizingData": "Synchronize data to standby site error", | ||||||
|     "RedundancyDisable": "Redundant gateway site not enabled", |     "RedundancyDisable": "Redundant gateway site not enabled", | ||||||
|     "RedundancyDup": "Redundant station settings duplicated", |     "RedundancyDup": "Redundant station settings duplicated", | ||||||
| @@ -67,10 +89,10 @@ | |||||||
|     "SwitchNormalState": "Local machine (primary site) will switch to normal state", |     "SwitchNormalState": "Local machine (primary site) will switch to normal state", | ||||||
|     "SwitchSlaveState": "Master site has recovered, local machine (standby) will switch to standby state" |     "SwitchSlaveState": "Master site has recovered, local machine (standby) will switch to standby state" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Management.RedundancyOptions": { |   "ThingsGateway.Gateway.Application.RedundancyOptions": { | ||||||
|     "Confirm": "Confirm switching to redundant state", |     "Confirm": "Confirm switching to redundant state", | ||||||
|     "Enable": "Enable Dual-Machine Redundancy", |     "Enable": "Enable Dual-Machine Redundancy", | ||||||
|     "ForcedSync": "Forced Synchronous", |     "RedundancyForcedSync": "Forced Synchronous", | ||||||
|     "ForcedSyncWarning": "Forcing synchronization will generate database configuration information.Are you sure you want to continue?", |     "ForcedSyncWarning": "Forcing synchronization will generate database configuration information.Are you sure you want to continue?", | ||||||
|     "HeartbeatInterval": "Heartbeat Interval", |     "HeartbeatInterval": "Heartbeat Interval", | ||||||
|     "IsMaster": "IsMaster", |     "IsMaster": "IsMaster", | ||||||
| @@ -86,13 +108,13 @@ | |||||||
|     "SyncInterval": "Data Synchronization Interval", |     "SyncInterval": "Data Synchronization Interval", | ||||||
|     "VerifyToken": "Verification Token" |     "VerifyToken": "Verification Token" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Management.RedundancyService": { |   "ThingsGateway.Gateway.Application.RedundancyService": { | ||||||
|     "EditRedundancyOption": "EditRedundancyOption" |     "EditRedundancyOption": "EditRedundancyOption" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Management.UpdateZipFileHostedService": { |   "ThingsGateway.Gateway.Application.UpdateZipFileService": { | ||||||
|     "Update": "New version detected" |     "Update": "New version detected" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Upgrade.UpdateZipFile": { |   "ThingsGateway.Gateway.Application.UpdateZipFile": { | ||||||
|     "AppName": "AppName", |     "AppName": "AppName", | ||||||
|     "Architecture": "Architecture", |     "Architecture": "Architecture", | ||||||
|     "DotNetVersion": "DotNetVersion", |     "DotNetVersion": "DotNetVersion", | ||||||
| @@ -106,6 +128,7 @@ | |||||||
|   "ThingsGateway.Gateway.Application.AlarmVariable": { |   "ThingsGateway.Gateway.Application.AlarmVariable": { | ||||||
|     "AlarmCode": "AlarmCode", |     "AlarmCode": "AlarmCode", | ||||||
|     "AlarmDelay": "AlarmDelay", |     "AlarmDelay": "AlarmDelay", | ||||||
|  |     "AlarmLevel": "AlarmLevel", | ||||||
|     "AlarmEnable": "AlarmEnable", |     "AlarmEnable": "AlarmEnable", | ||||||
|     "AlarmLimit": "AlarmLimit", |     "AlarmLimit": "AlarmLimit", | ||||||
|     "AlarmText": "AlarmText", |     "AlarmText": "AlarmText", | ||||||
| @@ -132,6 +155,8 @@ | |||||||
|     "DeviceName": "DeviceName", |     "DeviceName": "DeviceName", | ||||||
|     "Enable": "Enable", |     "Enable": "Enable", | ||||||
|     "EventTime": "EventTime", |     "EventTime": "EventTime", | ||||||
|  |     "ConfirmTime": "ConfirmTime", | ||||||
|  |     "FinishTime": "FinishTime", | ||||||
|     "EventType": "EventType", |     "EventType": "EventType", | ||||||
|     "HAlarmCode": "HAlarmCode", |     "HAlarmCode": "HAlarmCode", | ||||||
|     "HAlarmEnable": "HAlarmEnable", |     "HAlarmEnable": "HAlarmEnable", | ||||||
| @@ -375,6 +400,7 @@ | |||||||
|     "BusinessDeviceName": "BusinessDevice", |     "BusinessDeviceName": "BusinessDevice", | ||||||
|     "ChannelName": "Channel", |     "ChannelName": "Channel", | ||||||
|     "DeviceName": "Device", |     "DeviceName": "Device", | ||||||
|  |     "AlarmName": "Alarm", | ||||||
|     "RedundantDeviceName": "Redundant Device", |     "RedundantDeviceName": "Redundant Device", | ||||||
|     "VariableName": "Variable" |     "VariableName": "Variable" | ||||||
|   }, |   }, | ||||||
| @@ -436,24 +462,16 @@ | |||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Gateway.Application.Variable": { |   "ThingsGateway.Gateway.Application.Variable": { | ||||||
|     "AddressOrOtherMethodNotNull": "Variable address or special method cannot be empty at the same time", |     "AddressOrOtherMethodNotNull": "Variable address or special method cannot be empty at the same time", | ||||||
|     "AlarmDelay": "AlarmDelay", |  | ||||||
|     "ArrayLength": "ArrayLength", |     "ArrayLength": "ArrayLength", | ||||||
|     "BoolCloseAlarmEnable": "BoolCloseAlarmEnable", |  | ||||||
|     "BoolCloseAlarmText": "BoolCloseAlarmText", |  | ||||||
|     "BoolCloseRestrainExpressions": "BoolCloseRestrainExpressions", |  | ||||||
|     "BoolOpenAlarmEnable": "BoolOpenAlarmEnable", |  | ||||||
|     "BoolOpenAlarmText": "BoolOpenAlarmText", |  | ||||||
|     "BoolOpenRestrainExpressions": "BoolOpenRestrainExpressions", |  | ||||||
|     "BusinessGroup": "BusinessGroup", |     "BusinessGroup": "BusinessGroup", | ||||||
|     "BusinessGroupUpdateTrigger": "BusinessGroupUpdateTrigger", |     "BusinessGroupUpdateTrigger": "BusinessGroupUpdateTrigger", | ||||||
|     "RpcWriteCheck": "RpcWriteCheck", |     "RpcWriteCheck": "RpcWriteCheck", | ||||||
|     "ClearVariable": "Clear Variable", |     "ClearVariable": "Clear Variable", | ||||||
|     "CollectGroup": "CollectGroup", |     "CollectGroup": "CollectGroup", | ||||||
|     "CopyVariable": "Copy Variable", |     "CopyVariable": "Copy Variable", | ||||||
|     "CustomAlarmCode": "CustomAlarmCode", |  | ||||||
|     "CustomAlarmEnable": "CustomAlarmEnable", |  | ||||||
|     "CustomAlarmText": "CustomAlarmText", |  | ||||||
|     "CustomRestrainExpressions": "CustomRestrainExpressions", |  | ||||||
|     "DataType": "DataType", |     "DataType": "DataType", | ||||||
|     "DeleteVariable": "Delete Variable", |     "DeleteVariable": "Delete Variable", | ||||||
|     "Description": "Description", |     "Description": "Description", | ||||||
| @@ -464,26 +482,14 @@ | |||||||
|     "DynamicVariable": "DynamicVariable", |     "DynamicVariable": "DynamicVariable", | ||||||
|     "Enable": "Enable", |     "Enable": "Enable", | ||||||
|     "ExportVariable": "Export Variable", |     "ExportVariable": "Export Variable", | ||||||
|     "HAlarmCode": "HAlarmCode", |  | ||||||
|     "HAlarmEnable": "HAlarmEnable", |  | ||||||
|     "HAlarmText": "HAlarmText", |  | ||||||
|     "HHAlarmCode": "HHAlarmCode", |  | ||||||
|     "HHAlarmEnable": "HHAlarmEnable", |  | ||||||
|     "HHAlarmText": "HHAlarmText", |  | ||||||
|     "HHRestrainExpressions": "HHRestrainExpressions", |  | ||||||
|     "HRestrainExpressions": "HRestrainExpressions", |  | ||||||
|     "ImportVariable": "Import Variable", |     "ImportVariable": "Import Variable", | ||||||
|     "InitValue": "InitValue", |     "InitValue": "InitValue", | ||||||
|     "IntervalTime": "IntervalTime", |     "IntervalTime": "IntervalTime", | ||||||
|     "IntervalTime.MinValue": "{0} value is too small", |     "IntervalTime.MinValue": "{0} value is too small", | ||||||
|     "LAlarmCode": "LAlarmCode", |  | ||||||
|     "LAlarmEnable": "LAlarmEnable", |  | ||||||
|     "LAlarmText": "LAlarmText", |  | ||||||
|     "LLAlarmCode": "LLAlarmCode", |  | ||||||
|     "LLAlarmEnable": "LLAlarmEnable", |  | ||||||
|     "LLAlarmText": "LLAlarmText", |  | ||||||
|     "LLRestrainExpressions": "LLRestrainExpressions", |  | ||||||
|     "LRestrainExpressions": "LRestrainExpressions", |  | ||||||
|     "Name": "Name", |     "Name": "Name", | ||||||
|     "Name.Required": "{0} cannot be empty", |     "Name.Required": "{0} cannot be empty", | ||||||
|     "NameDump": "Duplicate variable name {0}", |     "NameDump": "Duplicate variable name {0}", | ||||||
| @@ -505,6 +511,38 @@ | |||||||
|     "VariableNotNull": "Variable name does not exist", |     "VariableNotNull": "Variable name does not exist", | ||||||
|     "WriteExpressions": "WriteExpressions" |     "WriteExpressions": "WriteExpressions" | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |   "ThingsGateway.Gateway.Application.AlarmPropertys": { | ||||||
|  |  | ||||||
|  |     "AlarmDelay": "AlarmDelay", | ||||||
|  |     "AlarmLevel": "AlarmLevel", | ||||||
|  |     "BoolCloseAlarmEnable": "BoolCloseAlarmEnable", | ||||||
|  |     "BoolCloseAlarmText": "BoolCloseAlarmText", | ||||||
|  |     "BoolCloseRestrainExpressions": "BoolCloseRestrainExpressions", | ||||||
|  |     "BoolOpenAlarmEnable": "BoolOpenAlarmEnable", | ||||||
|  |     "BoolOpenAlarmText": "BoolOpenAlarmText", | ||||||
|  |     "BoolOpenRestrainExpressions": "BoolOpenRestrainExpressions", | ||||||
|  |     "CustomAlarmCode": "CustomAlarmCode", | ||||||
|  |     "CustomAlarmEnable": "CustomAlarmEnable", | ||||||
|  |     "CustomAlarmText": "CustomAlarmText", | ||||||
|  |     "CustomRestrainExpressions": "CustomRestrainExpressions", | ||||||
|  |     "HAlarmCode": "HAlarmCode", | ||||||
|  |     "HAlarmEnable": "HAlarmEnable", | ||||||
|  |     "HAlarmText": "HAlarmText", | ||||||
|  |     "HHAlarmCode": "HHAlarmCode", | ||||||
|  |     "HHAlarmEnable": "HHAlarmEnable", | ||||||
|  |     "HHAlarmText": "HHAlarmText", | ||||||
|  |     "HHRestrainExpressions": "HHRestrainExpressions", | ||||||
|  |     "HRestrainExpressions": "HRestrainExpressions", | ||||||
|  |     "LAlarmCode": "LAlarmCode", | ||||||
|  |     "LAlarmEnable": "LAlarmEnable", | ||||||
|  |     "LAlarmText": "LAlarmText", | ||||||
|  |     "LLAlarmCode": "LLAlarmCode", | ||||||
|  |     "LLAlarmEnable": "LLAlarmEnable", | ||||||
|  |     "LLAlarmText": "LLAlarmText", | ||||||
|  |     "LLRestrainExpressions": "LLRestrainExpressions", | ||||||
|  |     "LRestrainExpressions": "LRestrainExpressions" | ||||||
|  |   }, | ||||||
|   "ThingsGateway.Gateway.Application.VariableRuntime": { |   "ThingsGateway.Gateway.Application.VariableRuntime": { | ||||||
|     "AlarmCode": "AlarmCode", |     "AlarmCode": "AlarmCode", | ||||||
|     "AlarmEnable": "AlarmEnable", |     "AlarmEnable": "AlarmEnable", | ||||||
| @@ -519,6 +557,8 @@ | |||||||
|     "DeviceName": "DeviceName", |     "DeviceName": "DeviceName", | ||||||
|     "DynamicVariable": "DynamicVariable", |     "DynamicVariable": "DynamicVariable", | ||||||
|     "EventTime": "EventTime", |     "EventTime": "EventTime", | ||||||
|  |     "ConfirmTime": "ConfirmTime", | ||||||
|  |     "FinishTime": "FinishTime", | ||||||
|     "EventType": "EventType", |     "EventType": "EventType", | ||||||
|     "IntervalTime.MinValue": "{0} value is too small", |     "IntervalTime.MinValue": "{0} value is too small", | ||||||
|     "IsOnline": "IsOnline", |     "IsOnline": "IsOnline", | ||||||
|   | |||||||
| @@ -1,6 +1,34 @@ | |||||||
| { | { | ||||||
|  |   "ThingsGateway.Management.Application.ExportString": { | ||||||
|  |  | ||||||
|   "ThingsGateway.Gateway.Application.DefaultDiagram": { |     "ManagementConfigName": "通讯配置" | ||||||
|  |   }, | ||||||
|  |   "ThingsGateway.Management.Application.ManagementConfig": { | ||||||
|  |  | ||||||
|  |     "Name": "名称", | ||||||
|  |     "ServerUri": "服务端Url", | ||||||
|  |     "Enable": "启用", | ||||||
|  |     "IsServer": "服务端", | ||||||
|  |     "VerifyToken": "验证令牌", | ||||||
|  |     "HeartbeatInterval": "心跳间隔", | ||||||
|  |  | ||||||
|  |     "ImportNullError": "无法识别" | ||||||
|  |  | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   "ThingsGateway.Management.Application.UpdateZipFile": { | ||||||
|  |     "AppName": "名称", | ||||||
|  |     "Architecture": "架构", | ||||||
|  |     "DotNetVersion": ".net版本", | ||||||
|  |     "FilePath": "文件路径", | ||||||
|  |     "FileSize": "文件大小", | ||||||
|  |     "MinimumCompatibleVersion": "最小兼容版本", | ||||||
|  |     "OSPlatform": "系统版本", | ||||||
|  |     "Version": "版本" | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   "ThingsGateway.Gateway.Application.INode": { | ||||||
|  |  | ||||||
|     "Actuator": "执行", |     "Actuator": "执行", | ||||||
|     "AlarmChangedTriggerNode": "报警状态触发器", |     "AlarmChangedTriggerNode": "报警状态触发器", | ||||||
| @@ -51,11 +79,8 @@ | |||||||
|     "RulesId": "名称" |     "RulesId": "名称" | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   "ThingsGateway.Management.AutoUpdateController": { |  | ||||||
|     "AutoUpdateController": "程序更新", |   "ThingsGateway.Gateway.Application.RedundancyHostedService": { | ||||||
|     "Update": "更新" |  | ||||||
|   }, |  | ||||||
|   "ThingsGateway.Management.RedundancyHostedService": { |  | ||||||
|     "ErrorSynchronizingData": "同步数据到从站错误", |     "ErrorSynchronizingData": "同步数据到从站错误", | ||||||
|     "RedundancyDisable": "不启用网关冗余站点", |     "RedundancyDisable": "不启用网关冗余站点", | ||||||
|     "RedundancyDup": "主备站设置重复", |     "RedundancyDup": "主备站设置重复", | ||||||
| @@ -65,10 +90,10 @@ | |||||||
|     "SwitchNormalState": "本机(主站)将切换到正常状态", |     "SwitchNormalState": "本机(主站)将切换到正常状态", | ||||||
|     "SwitchSlaveState": "主站已恢复,本机(从站)将切换到备用状态" |     "SwitchSlaveState": "主站已恢复,本机(从站)将切换到备用状态" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Management.RedundancyOptions": { |   "ThingsGateway.Gateway.Application.RedundancyOptions": { | ||||||
|     "Confirm": "确认切换冗余状态", |     "Confirm": "确认切换冗余状态", | ||||||
|     "Enable": "启用双机冗余", |     "Enable": "启用双机冗余", | ||||||
|     "ForcedSync": "强制同步", |     "RedundancyForcedSync": "强制同步", | ||||||
|     "ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?", |     "ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?", | ||||||
|     "HeartbeatInterval": "心跳间隔", |     "HeartbeatInterval": "心跳间隔", | ||||||
|     "IsMaster": "是否为主站", |     "IsMaster": "是否为主站", | ||||||
| @@ -82,15 +107,15 @@ | |||||||
|     "Status": "当前站点状态", |     "Status": "当前站点状态", | ||||||
|     "Switch": "切换", |     "Switch": "切换", | ||||||
|     "SyncInterval": "数据同步间隔", |     "SyncInterval": "数据同步间隔", | ||||||
|     "VerifyToken": "Token" |     "VerifyToken": "验证令牌" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Management.RedundancyService": { |   "ThingsGateway.Gateway.Application.RedundancyService": { | ||||||
|     "EditRedundancyOption": "修改网关冗余配置" |     "EditRedundancyOption": "修改网关冗余配置" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Management.UpdateZipFileHostedService": { |   "ThingsGateway.Gateway.Application.UpdateZipFileService": { | ||||||
|     "Update": "检测到新版本" |     "Update": "检测到新版本" | ||||||
|   }, |   }, | ||||||
|   "ThingsGateway.Upgrade.UpdateZipFile": { |   "ThingsGateway.Gateway.Application.UpdateZipFile": { | ||||||
|     "AppName": "名称", |     "AppName": "名称", | ||||||
|     "Architecture": "架构", |     "Architecture": "架构", | ||||||
|     "DotNetVersion": ".net版本", |     "DotNetVersion": ".net版本", | ||||||
| @@ -105,6 +130,7 @@ | |||||||
|   "ThingsGateway.Gateway.Application.AlarmVariable": { |   "ThingsGateway.Gateway.Application.AlarmVariable": { | ||||||
|     "AlarmCode": "报警值", |     "AlarmCode": "报警值", | ||||||
|     "AlarmDelay": "报警延时", |     "AlarmDelay": "报警延时", | ||||||
|  |     "AlarmLevel": "报警等级", | ||||||
|     "AlarmEnable": "报警使能", |     "AlarmEnable": "报警使能", | ||||||
|     "AlarmLimit": "报警限值", |     "AlarmLimit": "报警限值", | ||||||
|     "AlarmText": "报警文本", |     "AlarmText": "报警文本", | ||||||
| @@ -131,6 +157,8 @@ | |||||||
|     "DeviceName": "设备名称", |     "DeviceName": "设备名称", | ||||||
|     "Enable": "变量使能", |     "Enable": "变量使能", | ||||||
|     "EventTime": "事件时间", |     "EventTime": "事件时间", | ||||||
|  |     "ConfirmTime": "确认时间", | ||||||
|  |     "FinishTime": "恢复时间", | ||||||
|     "EventType": "事件类型", |     "EventType": "事件类型", | ||||||
|     "HAlarmCode": "高限值", |     "HAlarmCode": "高限值", | ||||||
|     "HAlarmEnable": "高报使能", |     "HAlarmEnable": "高报使能", | ||||||
| @@ -376,6 +404,7 @@ | |||||||
|     "BusinessDeviceName": "业务设备", |     "BusinessDeviceName": "业务设备", | ||||||
|     "ChannelName": "通道", |     "ChannelName": "通道", | ||||||
|     "DeviceName": "设备", |     "DeviceName": "设备", | ||||||
|  |     "AlarmName": "报警", | ||||||
|     "RedundantDeviceName": "冗余设备", |     "RedundantDeviceName": "冗余设备", | ||||||
|     "VariableName": "变量" |     "VariableName": "变量" | ||||||
|   }, |   }, | ||||||
| @@ -438,6 +467,7 @@ | |||||||
|   "ThingsGateway.Gateway.Application.Variable": { |   "ThingsGateway.Gateway.Application.Variable": { | ||||||
|     "AddressOrOtherMethodNotNull": " 变量地址或特殊方法不能同时为空 ", |     "AddressOrOtherMethodNotNull": " 变量地址或特殊方法不能同时为空 ", | ||||||
|     "AlarmDelay": "报警延时", |     "AlarmDelay": "报警延时", | ||||||
|  |     "AlarmLevel": "报警等级", | ||||||
|     "ArrayLength": "数组长度", |     "ArrayLength": "数组长度", | ||||||
|     "BoolCloseAlarmEnable": "布尔关报警使能", |     "BoolCloseAlarmEnable": "布尔关报警使能", | ||||||
|     "BoolCloseAlarmText": "布尔关报警文本", |     "BoolCloseAlarmText": "布尔关报警文本", | ||||||
| @@ -506,6 +536,40 @@ | |||||||
|     "VariableNotNull": "变量名称不存在", |     "VariableNotNull": "变量名称不存在", | ||||||
|     "WriteExpressions": "写入表达式" |     "WriteExpressions": "写入表达式" | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |   "ThingsGateway.Gateway.Application.AlarmPropertys": { | ||||||
|  |  | ||||||
|  |     "AlarmDelay": "报警延时", | ||||||
|  |     "AlarmLevel": "报警等级", | ||||||
|  |     "BoolCloseAlarmEnable": "布尔关报警使能", | ||||||
|  |     "BoolCloseAlarmText": "布尔关报警文本", | ||||||
|  |     "BoolCloseRestrainExpressions": "布尔关报警约束", | ||||||
|  |     "BoolOpenAlarmEnable": "布尔开报警使能", | ||||||
|  |     "BoolOpenAlarmText": "布尔开报警文本", | ||||||
|  |     "BoolOpenRestrainExpressions": "布尔开报警约束", | ||||||
|  |     "CustomAlarmCode": "自定义报警限值", | ||||||
|  |     "CustomAlarmEnable": "自定义报警使能", | ||||||
|  |     "CustomAlarmText": "自定义报警文本", | ||||||
|  |     "CustomRestrainExpressions": "自定义报警约束", | ||||||
|  |     "HAlarmCode": "高限值", | ||||||
|  |     "HAlarmEnable": "高报使能", | ||||||
|  |     "HAlarmText": "高报文本", | ||||||
|  |     "HHAlarmCode": "高高限值", | ||||||
|  |     "HHAlarmEnable": "高高报使能", | ||||||
|  |     "HHAlarmText": "高高报文本", | ||||||
|  |     "HHRestrainExpressions": "高高报约束", | ||||||
|  |     "HRestrainExpressions": "高报约束", | ||||||
|  |     "LAlarmCode": "低限值", | ||||||
|  |     "LAlarmEnable": "低报使能", | ||||||
|  |     "LAlarmText": "低报文本", | ||||||
|  |     "LLAlarmCode": "低低限值", | ||||||
|  |     "LLAlarmEnable": "低低报使能", | ||||||
|  |     "LLAlarmText": "低低报文本", | ||||||
|  |     "LLRestrainExpressions": "低低报约束", | ||||||
|  |     "LRestrainExpressions": "低报约束" | ||||||
|  |  | ||||||
|  |   }, | ||||||
|  |  | ||||||
|   "ThingsGateway.Gateway.Application.VariableRuntime": { |   "ThingsGateway.Gateway.Application.VariableRuntime": { | ||||||
|     "AlarmCode": "报警值", |     "AlarmCode": "报警值", | ||||||
|     "AlarmEnable": "报警使能", |     "AlarmEnable": "报警使能", | ||||||
| @@ -520,6 +584,8 @@ | |||||||
|     "DeviceName": "设备名称", |     "DeviceName": "设备名称", | ||||||
|     "DynamicVariable": "动态变量", |     "DynamicVariable": "动态变量", | ||||||
|     "EventTime": "事件时间", |     "EventTime": "事件时间", | ||||||
|  |     "ConfirmTime": "确认时间", | ||||||
|  |     "FinishTime": "恢复时间", | ||||||
|     "EventType": "事件类型", |     "EventType": "事件类型", | ||||||
|     "IntervalTime.MinValue": " {0} 值太小", |     "IntervalTime.MinValue": " {0} 值太小", | ||||||
|     "IsOnline": "在线", |     "IsOnline": "在线", | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user