mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 15:43:59 +08:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			10.10.15.0
			...
			10.10.23.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8e3bd89f61 | ||
|   | 6da142d080 | ||
|   | ff7d029e6f | ||
|   | 21b4695683 | ||
|   | 02ad494a26 | ||
|   | 280366e1b2 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -364,6 +364,7 @@ FodyWeavers.xsd | ||||
|  | ||||
| /src/*Pro*/ | ||||
| /src/*Pro* | ||||
| /src/**/*Pro* | ||||
| /src/*pro* | ||||
| /src/*pro*/ | ||||
| /src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| 		<IncludeBuildOutput>false</IncludeBuildOutput> | ||||
| 		<!-- 避免 DLL 被打包到 lib/ --> | ||||
| 		<EnableSourceGenerator>true</EnableSourceGenerator> | ||||
| 		 | ||||
| 		<!-- 可选 --> | ||||
|  | ||||
| 		 | ||||
|   | ||||
| @@ -39,7 +39,7 @@ public class FileController : ControllerBase | ||||
|         var filePath = Path.Combine(wwwroot, fileName); | ||||
|         // 防止路径穿越攻击 | ||||
| #pragma warning disable CA3003 | ||||
|         if (!filePath.StartsWith(wwwroot, StringComparison.OrdinalIgnoreCase) || !System.IO.File.Exists(filePath)) | ||||
|         if (filePath.Contains("..") || !System.IO.File.Exists(filePath)) | ||||
|         { | ||||
|             return NotFound(); | ||||
|         } | ||||
| @@ -49,6 +49,6 @@ public class FileController : ControllerBase | ||||
|  | ||||
|         Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition"); | ||||
|  | ||||
|         return File(fileStream, "application/octet-stream", (fileName.Replace('/', '_'))); | ||||
|         return File(fileStream, "application/octet-stream", (Path.GetFileName(filePath).Replace('/', '_'))); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -21,16 +21,7 @@ | ||||
|     "UserNoModule": "This account has not been assigned a module. Please contact the administrator", | ||||
|     "UserNull": "User {0} does not exist" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.BaseDataEntity": { | ||||
|     "CreateOrgId": "CreateOrgId" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.BaseEntity": { | ||||
|     "CreateTime": "CreateTime", | ||||
|     "CreateUser": "CreateUser", | ||||
|     "SortCode": "SortCode", | ||||
|     "UpdateTime": "UpdateTime", | ||||
|     "UpdateUser": "UpdateUser" | ||||
|   }, | ||||
|    | ||||
|   "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { | ||||
|     "UserExpire": "User expired, please login again" | ||||
|   }, | ||||
|   | ||||
| @@ -21,16 +21,7 @@ | ||||
|     "UserNoModule": "该账号未分配模块,请联系管理员", | ||||
|     "UserNull": "用户 {0} 不存在" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.BaseDataEntity": { | ||||
|     "CreateOrgId": "创建机构Id" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.BaseEntity": { | ||||
|     "CreateTime": "创建时间", | ||||
|     "CreateUser": "创建人", | ||||
|     "SortCode": "排序", | ||||
|     "UpdateTime": "更新时间", | ||||
|     "UpdateUser": "更新人" | ||||
|   }, | ||||
|  | ||||
|   "ThingsGateway.Admin.Application.BlazorAuthenticationHandler": { | ||||
|     "UserExpire": "用户登录已过期,请重新登录" | ||||
|   }, | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
|   | ||||
| @@ -30,7 +30,7 @@ public class BlazorAppContext | ||||
|     /// <summary> | ||||
|     /// 全部菜单 | ||||
|     /// </summary> | ||||
|     public IEnumerable<SysResource> AllMenus { get; private set; } | ||||
|     public List<SysResource> AllMenus { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前用户 | ||||
| @@ -42,22 +42,22 @@ public class BlazorAppContext | ||||
|     /// <summary> | ||||
|     /// 用户个人菜单 | ||||
|     /// </summary> | ||||
|     public IEnumerable<MenuItem> OwnMenuItems { get; private set; } | ||||
|     public List<MenuItem> OwnMenuItems { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 不同模块的菜单 | ||||
|     /// </summary> | ||||
|     public IEnumerable<MenuItem> AllOwnMenuItems { get; private set; } | ||||
|     public List<MenuItem> AllOwnMenuItems { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户个人菜单,多个模块 | ||||
|     /// </summary> | ||||
|     public IEnumerable<SysResource> OwnMenus { get; private set; } | ||||
|     public List<SysResource> OwnMenus { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 用户个人菜单,非树形 | ||||
|     /// </summary> | ||||
|     public IEnumerable<MenuItem> OwnSameLevelMenuItems { get; private set; } | ||||
|     public List<MenuItem> OwnSameLevelMenuItems { get; private set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 个人工作台 | ||||
| @@ -67,9 +67,9 @@ public class BlazorAppContext | ||||
|     /// <summary> | ||||
|     /// 用户个人快捷方式菜单 | ||||
|     /// </summary> | ||||
|     public IEnumerable<SysResource> UserWorkbenchOutputs { get; private set; } | ||||
|     public List<SysResource> UserWorkbenchOutputs { get; private set; } | ||||
|  | ||||
|     public IEnumerable<SysResource> AllResource { get; private set; } | ||||
|     public List<SysResource> AllResource { get; private set; } | ||||
|  | ||||
|     private ISysResourceService ResourceService { get; } | ||||
|     private ISysUserService SysUserService { get; } | ||||
| @@ -93,7 +93,7 @@ public class BlazorAppContext | ||||
|             AllResource = sysResources; | ||||
|             var ids = CurrentUser.ModuleList.Select(a => a.Id).ToHashSet(); | ||||
|             CurrentUser.ModuleList = AllResource.Where(a => ids.Contains(a.Id)).OrderBy(a => a.SortCode).ToList(); | ||||
|             AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu); | ||||
|             AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu).ToList(); | ||||
|  | ||||
|             if (moduleId == null) | ||||
|             { | ||||
| @@ -123,8 +123,8 @@ public class BlazorAppContext | ||||
|                 } | ||||
|             } | ||||
|             var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId); | ||||
|             OwnMenuItems = ResourceUtil.BuildMenuTrees(ownMenus).ToList(); | ||||
|             AllOwnMenuItems = ResourceUtil.BuildMenuTrees(OwnMenus).ToList(); | ||||
|             OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList(); | ||||
|             AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList(); | ||||
|             OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem() | ||||
|             { | ||||
|                 Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All, | ||||
| @@ -132,8 +132,8 @@ public class BlazorAppContext | ||||
|                 Icon = item.Icon, | ||||
|                 Url = item.Href, | ||||
|                 Target = item.Target.ToString(), | ||||
|             }); | ||||
|             UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)); | ||||
|             }).ToList(); | ||||
|             UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -29,7 +29,7 @@ public partial class EditPagePolicy | ||||
|  | ||||
|     protected override Task OnParametersSetAsync() | ||||
|     { | ||||
|         ShortcutsTreeViewItems = ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null); | ||||
|         ShortcutsTreeViewItems = AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null); | ||||
|         return base.OnParametersSetAsync(); | ||||
|     } | ||||
|  | ||||
| @@ -48,6 +48,6 @@ public partial class EditPagePolicy | ||||
|     { | ||||
|         await Task.CompletedTask; | ||||
|         ShortcutsSearchText = searchText; | ||||
|         return ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null); | ||||
|         return AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -41,7 +41,7 @@ public partial class MenuChoiceDialog | ||||
|         var all = (await SysResourceService.GetAllAsync()); | ||||
|         var items = all.Where(a => a.Category == ResourceCategoryEnum.Menu && a.Module == ModuleId); | ||||
|         ModuleTitle = all.FirstOrDefault(a => a.Id == ModuleId)?.Title; | ||||
|         Items = ResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem); | ||||
|         Items = AdminResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem); | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -26,12 +26,12 @@ | ||||
|                 OnQueryAsync="OnQueryAsync" CustomerSearchModel="@CustomerSearchModel" | ||||
|                 OnSaveAsync="Save" OnDeleteAsync="Delete"> | ||||
|         <TableToolbarTemplate> | ||||
|             <PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count<=0||!AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy"> | ||||
|             <PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count <= 0 || !AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy"> | ||||
|                 <BodyTemplate> | ||||
|                     <Select Items="ModuleSelectedItems" @bind-Value=CopyModule ShowLabel="false" /> | ||||
|                 </BodyTemplate> | ||||
|             </PopConfirmButton> | ||||
|             <PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count!=1||!AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent"> | ||||
|             <PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count != 1 || !AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent"> | ||||
|                 <BodyTemplate> | ||||
|                     <div class="overflow-y-auto" style="height:500px"> | ||||
|                         <TreeView Items="MenuTreeItems" IsVirtualize="true" OnTreeItemClick="a=>{ChangeParentId=a.Value.Id;return Task.CompletedTask;}" /> | ||||
|   | ||||
| @@ -39,8 +39,8 @@ public partial class SysResourcePage | ||||
|  | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList(); | ||||
|         MenuItems = ResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList(); | ||||
|         ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList(); | ||||
|         MenuItems = AdminResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList(); | ||||
|  | ||||
|         await base.OnParametersSetAsync(); | ||||
|     } | ||||
| @@ -49,7 +49,7 @@ public partial class SysResourcePage | ||||
|  | ||||
|     private async Task<QueryData<SysResource>> OnQueryAsync(QueryPageOptions options) | ||||
|     { | ||||
|         MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(ResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList(); | ||||
|         MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(AdminResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList(); | ||||
|  | ||||
|         var data = await SysResourceService.PageAsync(options, CustomerSearchModel); | ||||
|         return data; | ||||
| @@ -136,14 +136,14 @@ public partial class SysResourcePage | ||||
|     private async Task<IEnumerable<TableTreeNode<SysResource>>> OnTreeExpand(SysResource menu) | ||||
|     { | ||||
|         var sysResources = await SysResourceService.GetAllAsync(); | ||||
|         var result = ResourceUtil.BuildTableTrees(sysResources, menu.Id); | ||||
|         var result = AdminResourceUtil.BuildTableTrees(sysResources, menu.Id); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private static async Task<IEnumerable<TableTreeNode<SysResource>>> TreeNodeConverter(IEnumerable<SysResource> items) | ||||
|     { | ||||
|         await Task.CompletedTask; | ||||
|         var result = ResourceUtil.BuildTableTrees(items, 0); | ||||
|         var result = AdminResourceUtil.BuildTableTrees(items, 0); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -35,7 +35,7 @@ public partial class GrantResourceDialog | ||||
|     { | ||||
|         var items = (await SysResourceService.GetAllAsync()).Where(a => a.Category != ResourceCategoryEnum.Module).OrderBy(a => a.Module).ThenBy(a => a.Id).ToList(); | ||||
|  | ||||
|         Items = ResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem); | ||||
|         Items = AdminResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem); | ||||
|         ModuleList = (await SysResourceService.GetAllAsync()).Where(a => a.Category == ResourceCategoryEnum.Module).ToList(); | ||||
|         await base.OnInitializedAsync(); | ||||
|     } | ||||
|   | ||||
| @@ -35,7 +35,7 @@ public partial class SysUserEdit | ||||
|         BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.Status)); | ||||
|         var items = await SysPositionService.SelectorAsync(new PositionSelectorInput()); | ||||
|         Items = PositionUtil.BuildCascaderItemList(items); | ||||
|         ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList(); | ||||
|         ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList(); | ||||
|         await InvokeAsync(StateHasChanged); | ||||
|         await base.OnInitializedAsync(); | ||||
|     } | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0</TargetFrameworks> | ||||
| 		 | ||||
| 		<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>--> | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Razor; | ||||
| 
 | ||||
| /// <inheritdoc/> | ||||
| [ThingsGateway.DependencyInjection.SuppressSniffer] | ||||
| public static class ResourceUtil | ||||
| public static class AdminResourceUtil | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 构造选择项,ID/TITLE | ||||
| @@ -29,7 +29,7 @@ | ||||
|   <Target Name="AdminPostPublish" AfterTargets="Publish"> | ||||
|     <ItemGroup> | ||||
|       <!-- setting up the variable for convenience --> | ||||
|       <AdminFiles Include="bin\$(Configuration)\$(TargetFramework)\SeedData\**" /> | ||||
|       <AdminFiles Include="$(OutputPath)\$(TargetFramework)\SeedData\**" /> | ||||
|     </ItemGroup> | ||||
|     <PropertyGroup> | ||||
|     </PropertyGroup> | ||||
|   | ||||
| @@ -5,7 +5,8 @@ | ||||
| #推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway | ||||
|  | ||||
| #aspnetcore9.0环境 | ||||
| FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base | ||||
| #FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base | ||||
| FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble AS base | ||||
| COPY .  /app | ||||
| WORKDIR /app | ||||
| #默认web | ||||
| @@ -13,6 +14,8 @@ EXPOSE 5000 | ||||
|  | ||||
| # 添加时区环境变量,亚洲,上海 | ||||
| ENV TimeZone=Asia/Shanghai | ||||
| # 转发头 | ||||
| ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true | ||||
| # 使用软连接,并且将时区配置覆盖/etc/timezone | ||||
| RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,8 @@ | ||||
| #推送:docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64 | ||||
|  | ||||
| #aspnetcore9.0环境 | ||||
| FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8  AS base | ||||
| #FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8  AS base | ||||
| FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-arm64v8 AS base | ||||
| COPY .  /app | ||||
| WORKDIR /app | ||||
| #默认web | ||||
| @@ -13,6 +14,8 @@ EXPOSE 5000 | ||||
|  | ||||
| # 添加时区环境变量,亚洲,上海 | ||||
| ENV TimeZone=Asia/Shanghai | ||||
| # 转发头 | ||||
| ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true | ||||
| # 使用软连接,并且将时区配置覆盖/etc/timezone | ||||
| RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<!--<Import Project="Admin.targets" Condition=" '$(Configuration)' != 'Debug' " />--> | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| @@ -13,7 +14,7 @@ | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" /> | ||||
| 		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.9.1" /> | ||||
| 		<PackageReference Include="BootstrapBlazor" Version="9.9.2" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -53,6 +53,8 @@ public static class QueryPageOptionsExtensions | ||||
|         return datas; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null) | ||||
|     { | ||||
|         if (queryFunc != null) | ||||
| @@ -135,4 +137,24 @@ public static class QueryPageOptionsExtensions | ||||
|         ret.Items = items.ToList(); | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 根据查询条件返回QueryData | ||||
|     /// </summary> | ||||
|     public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null) | ||||
|     { | ||||
|         var ret = new QueryData<SelectedItem>() | ||||
|         { | ||||
|             IsSorted = false, | ||||
|             IsFiltered = false, | ||||
|             IsAdvanceSearch = false, | ||||
|             IsSearch = !option.SearchText.IsNullOrWhiteSpace() | ||||
|         }; | ||||
|  | ||||
|         var items = datas.Skip((option.StartIndex)).Take(option.Count); | ||||
|         ret.TotalCount = datas.Count(); | ||||
|  | ||||
|         ret.Items = func(items).ToList(); | ||||
|         return ret; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/en-US.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/en-US.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| { | ||||
|   | ||||
|   "ThingsGateway.Admin.Application.BaseDataEntity": { | ||||
|     "CreateOrgId": "CreateOrgId" | ||||
|   }, | ||||
|   "ThingsGateway.Admin.Application.BaseEntity": { | ||||
|     "CreateTime": "CreateTime", | ||||
|     "CreateUser": "CreateUser", | ||||
|     "SortCode": "SortCode", | ||||
|     "UpdateTime": "UpdateTime", | ||||
|     "UpdateUser": "UpdateUser" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/zh-CN.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Admin/ThingsGateway.DB/Locales/zh-CN.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| { | ||||
|    | ||||
|   "ThingsGateway.DB.BaseDataEntity": { | ||||
|     "CreateOrgId": "创建机构Id" | ||||
|   }, | ||||
|   "ThingsGateway.DB.BaseEntity": { | ||||
|     "CreateTime": "创建时间", | ||||
|     "CreateUser": "创建人", | ||||
|     "SortCode": "排序", | ||||
|     "UpdateTime": "更新时间", | ||||
|     "UpdateUser": "更新人" | ||||
|   } | ||||
| } | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| @@ -18,6 +19,12 @@ | ||||
| 		<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <EmbeddedResource Include="Locales\en-US.json" /> | ||||
| 	  <EmbeddedResource Include="Locales\zh-CN.json" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<!--<PackageReference Include="ThingsGateway.Razor" Version="$(SourceGeneratorVersion)" />--> | ||||
| 		<!--<ProjectReference Include="..\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />--> | ||||
|   | ||||
| @@ -554,10 +554,9 @@ public static class App | ||||
|         { | ||||
|             types = ass.GetTypes(); | ||||
|         } | ||||
|         catch | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             XTrace.Log.Warn($"Error load `{ass.FullName}` assembly."); | ||||
|             Console.WriteLine($"Error load `{ass.FullName}` assembly."); | ||||
|             XTrace.Log.Warn($"Error load `{ass.FullName}` assembly. : {ex.Message}"); | ||||
|         } | ||||
|  | ||||
|         return types.Where(u => u.IsPublic && !u.IsDefined(typeof(SuppressSnifferAttribute), false)); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	 | ||||
| 	<PropertyGroup> | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| 		<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile> | ||||
| 		 | ||||
|  | ||||
|  | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| <Step @ref="@step" IsVertical="true"> | ||||
|     <StepItem Text=@Localizer["First"] Title=@Localizer["Upload"]> | ||||
|         <InputUpload ShowDeleteButton="true" @bind-Value=_importFile Accept=".xlsx"></InputUpload> | ||||
|         <Button class="mt-2" IsAsync OnClick="() => DeviceImport(_importFile)">@Localizer["Validate"]</Button> | ||||
|         <PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton> | ||||
|     </StepItem> | ||||
|     <StepItem Text=@Localizer["Second"] Title=@Localizer["ValidateText"]> | ||||
|  | ||||
| @@ -41,16 +41,12 @@ | ||||
|  | ||||
|             } | ||||
|  | ||||
|             <PopConfirmButton IsAsync IsDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton> | ||||
|             <Button class="mt-2" IsAsync OnClick="() => DeviceImport()">@RazorLocalizer["Close"]</Button> | ||||
|  | ||||
| @*  | ||||
|             <Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@ | ||||
|  | ||||
|         </div> | ||||
|     </StepItem> | ||||
| @*     <StepItem Text=@Localizer["Third"] Title=@Localizer["Import"]> | ||||
|         <PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton> | ||||
|     </StepItem> *@ | ||||
|  | ||||
| </Step> | ||||
| @code { | ||||
|     [NotNull] | ||||
|   | ||||
| @@ -24,18 +24,17 @@ public partial class ImportExcel | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     [EditorRequired] | ||||
|     public Func<Dictionary<string, ImportPreviewOutputBase>, Task> Import { get; set; } | ||||
|     public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Import { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     [NotNull] | ||||
|     private IStringLocalizer<ImportExcel>? Localizer { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 预览 | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     [EditorRequired] | ||||
|     public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Preview { get; set; } | ||||
|     [Inject] | ||||
|     [NotNull] | ||||
|     private IStringLocalizer<ThingsGateway.Razor._Imports>? RazorLocalizer { get; set; } | ||||
|  | ||||
|  | ||||
|  | ||||
|     [Inject] | ||||
|     [NotNull] | ||||
| @@ -47,13 +46,17 @@ public partial class ImportExcel | ||||
|     [CascadingParameter] | ||||
|     private Func<Task>? OnCloseAsync { get; set; } | ||||
|  | ||||
|     private async Task DeviceImport(IBrowserFile file) | ||||
|     private async Task DeviceImport() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             _importPreviews.Clear(); | ||||
|             _importPreviews = await Preview.Invoke(file); | ||||
|             await step.Next(); | ||||
|             await InvokeAsync(async () => | ||||
|             { | ||||
|                 if (OnCloseAsync != null) | ||||
|                     await OnCloseAsync(); | ||||
|                 await ToastService.Default(); | ||||
|             }); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
| @@ -67,16 +70,12 @@ public partial class ImportExcel | ||||
|         { | ||||
|             await Task.Run(async () => | ||||
|             { | ||||
|                 await Import.Invoke(_importPreviews); | ||||
|                 _importPreviews = await Import.Invoke(_importFile); | ||||
|                 _importFile = null; | ||||
|  | ||||
|                 await InvokeAsync(async () => | ||||
|                 { | ||||
|                     if (OnCloseAsync != null) | ||||
|                         await OnCloseAsync(); | ||||
|                     await ToastService.Default(); | ||||
|                 }); | ||||
|             }); | ||||
|             await step.Next(); | ||||
|  | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|   | ||||
| @@ -0,0 +1,58 @@ | ||||
| @using ThingsGateway.Extension | ||||
| @namespace ThingsGateway.Razor | ||||
| <Button OnClick="() => step.Reset()">@Localizer["Reset"]</Button> | ||||
| <h6 class="my-3 green--text">@Localizer["Tip"] </h6> | ||||
| <Step @ref="@step" IsVertical="true"> | ||||
|     <StepItem Text=@Localizer["First"] Title=@Localizer["Upload"]> | ||||
|         <InputUpload ShowDeleteButton="true" @bind-Value=_importFile Accept=".xlsx"></InputUpload> | ||||
|         <Button class="mt-2" IsAsync OnClick="() => DeviceImport(_importFile)">@Localizer["Validate"]</Button> | ||||
|     </StepItem> | ||||
|     <StepItem Text=@Localizer["Second"] Title=@Localizer["ValidateText"]> | ||||
|  | ||||
|         <div class="overflow-y-auto"> | ||||
|  | ||||
|             @foreach (var item in _importPreviews) | ||||
|             { | ||||
|                 <div class="mt-2"> | ||||
|                     @( | ||||
|                                     Localizer["UploadCount", item.Key, item.Value.DataCount] | ||||
|                                     ) | ||||
|                 </div> | ||||
|                 <div class=@((item.Value.HasError ? "my-2 red--text" : "my-2 green--text"))> | ||||
|                     @( | ||||
|                                     (item.Value.HasError ? "Error" : "Success") | ||||
|                                     ) | ||||
|             </div> | ||||
|                         if (item.Value.HasError) | ||||
|                 { | ||||
|                     <div style="height:300px;" class="overflow-y-scroll"> | ||||
|                         <Virtualize Items="item.Value.Results.Where(a => !a.Success).OrderBy(a => a.Row).ToList()" Context="item1" ItemSize="60" OverscanCount=2> | ||||
|                             <ItemContent> | ||||
|                                 <div class="row g-0"> | ||||
|                                     <span class="col mx-2">@item1.Row</span> | ||||
|                                     <span class=@((item1.Success ? "green--text col-auto" : "red--text col-auto"))> | ||||
|                                         <strong>@item1.ErrorMessage</strong> | ||||
|                                     </span> | ||||
|                                 </div> | ||||
|                             </ItemContent> | ||||
|                         </Virtualize> | ||||
|                     </div> | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|  | ||||
|             <PopConfirmButton IsAsync IsKeepDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton> | ||||
|  | ||||
| @*  | ||||
|             <Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@ | ||||
|  | ||||
|         </div> | ||||
|     </StepItem> | ||||
| @*     <StepItem Text=@Localizer["Third"] Title=@Localizer["Import"]> | ||||
|         <PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton> | ||||
|     </StepItem> *@ | ||||
| </Step> | ||||
| @code { | ||||
|     [NotNull] | ||||
|     Step? step { get; set; } | ||||
| } | ||||
| @@ -0,0 +1,86 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://thingsgateway.cn/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Razor; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public partial class ImportExcelConfirm | ||||
| { | ||||
|     private Dictionary<string, ImportPreviewOutputBase> _importPreviews = new(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导入 | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     [EditorRequired] | ||||
|     public Func<Dictionary<string, ImportPreviewOutputBase>, Task> Import { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     [NotNull] | ||||
|     private IStringLocalizer<ImportExcelConfirm>? Localizer { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 预览 | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     [EditorRequired] | ||||
|     public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Preview { get; set; } | ||||
|  | ||||
|     [Inject] | ||||
|     [NotNull] | ||||
|     private ToastService? ToastService { get; set; } | ||||
|  | ||||
|     [Required] | ||||
|     private IBrowserFile _importFile { get; set; } | ||||
|  | ||||
|     [CascadingParameter] | ||||
|     private Func<Task>? OnCloseAsync { get; set; } | ||||
|  | ||||
|     private async Task DeviceImport(IBrowserFile file) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             _importPreviews.Clear(); | ||||
|             _importPreviews = await Preview.Invoke(file); | ||||
|             await step.Next(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             await ToastService.Warn(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task SaveDeviceImport() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await Task.Run(async () => | ||||
|             { | ||||
|                 await Import.Invoke(_importPreviews); | ||||
|                 _importFile = null; | ||||
|  | ||||
|                 await InvokeAsync(async () => | ||||
|                 { | ||||
|                     if (OnCloseAsync != null) | ||||
|                         await OnCloseAsync(); | ||||
|                     await ToastService.Default(); | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             await InvokeAsync(async () => await ToastService.Warn(ex)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,9 @@ | ||||
| ::deep .avatar { | ||||
|     border-radius: 1.5rem; | ||||
|     width: 24px; | ||||
|     height: 24px; | ||||
|     background-color: var(--bs-red); | ||||
|     color: #fff; | ||||
|     flex: 0 0 auto; | ||||
|     font-size: 1rem; | ||||
| } | ||||
| @@ -25,7 +25,8 @@ | ||||
|     "Success": "Success", | ||||
|     "TablesExportButtonExcelText": "Export Excel", | ||||
|     "TablesImportButtonExcelText": "Import Excel", | ||||
|     "True": "Yes" | ||||
|     "True": "Yes", | ||||
|     "Info": "Info" | ||||
|   }, | ||||
|   "ThingsGateway.Razor.About": { | ||||
|     "Community": "Community", | ||||
| @@ -59,6 +60,19 @@ | ||||
|     "SearchText": "Search Page" | ||||
|   }, | ||||
|   "ThingsGateway.Razor.ImportExcel": { | ||||
|     "First": "Step 1", | ||||
|     "Import": "If there are no errors during verification, it will be directly imported into the database", | ||||
|     "Next": "Next", | ||||
|     "Reset": "Reset", | ||||
|     "Second": "Step 2", | ||||
|     "Third": "Step 3", | ||||
|     "Tip": "When the data volume is large (more than 200,000), the import may take more than 1 minute, please be patient", | ||||
|     "Upload": "Upload File", | ||||
|     "UploadCount": " Table {0}, import {1} records", | ||||
|     "Validate": "Validate", | ||||
|     "ValidateText": "Validation Content" | ||||
|   }, | ||||
|   "ThingsGateway.Razor.ImportExcelConfirm": { | ||||
|     "First": "Step 1", | ||||
|     "Import": "Import", | ||||
|     "Next": "Next", | ||||
| @@ -67,7 +81,7 @@ | ||||
|     "Third": "Step 3", | ||||
|     "Tip": "When the data volume is large (more than 200,000), the import may take more than 1 minute, please be patient", | ||||
|     "Upload": "Upload File", | ||||
|     "UploadCount": " Table {0}, expecting to import {1} records", | ||||
|     "UploadCount": " Table {0}, import {1} records", | ||||
|     "Validate": "Validate", | ||||
|     "ValidateText": "Validation Content" | ||||
|   }, | ||||
|   | ||||
| @@ -25,7 +25,8 @@ | ||||
|     "Success": "成功", | ||||
|     "TablesExportButtonExcelText": "导出Excel", | ||||
|     "TablesImportButtonExcelText": "导入Excel", | ||||
|     "True": "是" | ||||
|     "True": "是", | ||||
|     "Info": "详情" | ||||
|   }, | ||||
|   "ThingsGateway.Razor.About": { | ||||
|     "Community": "社区", | ||||
| @@ -59,6 +60,19 @@ | ||||
|     "SearchText": "搜索页面" | ||||
|   }, | ||||
|   "ThingsGateway.Razor.ImportExcel": { | ||||
|     "First": "第一步", | ||||
|     "Import": "若验证无错误,将直接导入数据库", | ||||
|     "Next": "下一步", | ||||
|     "Reset": "重置", | ||||
|     "Second": "第二步", | ||||
|     "Third": "第三", | ||||
|     "Tip": "数据量较大时(大于20万),所需导入时间可能超过1分钟,请耐心等待", | ||||
|     "Upload": "上传文件", | ||||
|     "UploadCount": " 表 {0},导入 {1} 条数据", | ||||
|     "Validate": "验证", | ||||
|     "ValidateText": "验证内容" | ||||
|   }, | ||||
|   "ThingsGateway.Razor.ImportExcelConfirm": { | ||||
|     "First": "第一步", | ||||
|     "Import": "导入", | ||||
|     "Next": "下一步", | ||||
| @@ -67,7 +81,7 @@ | ||||
|     "Third": "第三", | ||||
|     "Tip": "数据量较大时(大于20万),所需导入时间可能超过1分钟,请耐心等待", | ||||
|     "Upload": "上传文件", | ||||
|     "UploadCount": " 表 {0},预计导入 {1} 条数据", | ||||
|     "UploadCount": " 表 {0},导入 {1} 条数据", | ||||
|     "Validate": "验证", | ||||
|     "ValidateText": "验证内容" | ||||
|   }, | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0</TargetFrameworks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.0" /> | ||||
|   | ||||
| @@ -132,13 +132,16 @@ namespace ThingsGateway.SqlSugar | ||||
|             { | ||||
|                 // 准备多部分表单数据 | ||||
|                 var boundary = "---------------" + DateTime.Now.Ticks.ToString("x"); | ||||
|                 var list = new List<Hashtable>(); | ||||
|  | ||||
|                 tableName ??= db.EntityMaintenance.GetEntityInfo<T>().DbTableName; | ||||
|  | ||||
|                 // 获取或创建列信息缓存 | ||||
|                 var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode(); | ||||
|                 var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () => | ||||
|                  db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName)); | ||||
|                 var list = ReflectionInoCacheService.Instance.GetOrCreate($"{key}{dateFormat}List<Hashtable>", () => | ||||
|                 { | ||||
|                     var list = new List<Hashtable>(); | ||||
|  | ||||
|                     // 构建schema信息 | ||||
|                     columns.ForEach(d => | ||||
| @@ -161,6 +164,11 @@ namespace ThingsGateway.SqlSugar | ||||
|                         }); | ||||
|                         } | ||||
|                     }); | ||||
|  | ||||
|                     return list; | ||||
|                 } | ||||
|  ); | ||||
|  | ||||
|                 var schema = JsonConvert.SerializeObject(list); | ||||
|  | ||||
|                 // 写入CSV文件 | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0;</TargetFrameworks> | ||||
| @@ -29,11 +30,11 @@ | ||||
| 		<PackageReference Include="MySqlConnector" Version="2.4.0" /> | ||||
| 		<PackageReference Include="Npgsql" Version="9.0.3" /> | ||||
| 		<PackageReference Include="CsvHelper" Version="33.1.0" /> | ||||
| 		<PackageReference Include="TDengine.Connector" Version="3.1.7" /> | ||||
| 		<PackageReference Include="TDengine.Connector" Version="3.1.8" /> | ||||
| 		<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" /> | ||||
| 		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.21" /> | ||||
| 		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.23" /> | ||||
| 		<PackageReference Include="System.Data.Common" Version="4.3.0" /> | ||||
| 		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" /> | ||||
| 		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" /> | ||||
| 		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> | ||||
| 		<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" /> | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| <Project> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<PluginVersion>10.10.12</PluginVersion> | ||||
| 		<ProPluginVersion>10.10.12</ProPluginVersion> | ||||
| 		<DefaultVersion>10.10.15</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.10.1</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion> | ||||
| 		<PluginVersion>10.10.23</PluginVersion> | ||||
| 		<ProPluginVersion>10.10.23</ProPluginVersion> | ||||
| 		<DefaultVersion>10.10.23</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.10.2</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.10.2</SourceGeneratorVersion> | ||||
| 		<NET8Version>8.0.19</NET8Version> | ||||
| 		<NET9Version>9.0.8</NET9Version> | ||||
| 		<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages> | ||||
|   | ||||
							
								
								
									
										3088
									
								
								src/Drivers/ThingsGateway.AllenBradley.deps.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3088
									
								
								src/Drivers/ThingsGateway.AllenBradley.deps.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/Drivers/ThingsGateway.AllenBradley.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/Drivers/ThingsGateway.AllenBradley.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -4,10 +4,11 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>netstandard2.0;</TargetFrameworks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="CS-Script" Version="4.10.1" /> | ||||
| 		<PackageReference Include="CS-Script" Version="4.11.0" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
|             <EditorItem @bind-Field=Model.EncodingName> | ||||
|                 <EditTemplate Context="value"> | ||||
|                     <div class="col-12 col-sm-4"> | ||||
|                         <Select @bind-Value=value.EncodingName Items="EncodingItems" /> | ||||
|                         <Select @bind-Value=value.EncodingName Items="EncodingItems" IsClearable/> | ||||
|                     </div> | ||||
|                 </EditTemplate> | ||||
|             </EditorItem> | ||||
|   | ||||
| @@ -31,6 +31,6 @@ public partial class ConverterConfigComponent : ComponentBase | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.VariableStringLength), true); | ||||
|         EncodingItems = new List<SelectedItem>() { new SelectedItem("", "none") }.Concat(Encoding.GetEncodings().Select(a => new SelectedItem(a.CodePage.ToString(), a.DisplayName))).ToList(); | ||||
|         EncodingItems = Encoding.GetEncodings().Select(a => new SelectedItem(a.CodePage.ToString(), a.DisplayName)).ToList(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -40,7 +40,7 @@ public class PlatformService : IPlatformService | ||||
|             await using var jSObject = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"{WebsiteConst.DefaultResourceUrl}js/downloadFile.js"); | ||||
|             var path = Path.GetRelativePath("wwwroot", item); | ||||
|             string fileName = DateTime.Now.ToFileDateTimeFormat(); | ||||
|             await jSObject.InvokeVoidAsync("blazor_downloadFile", url, fileName, new { FileName = path }); | ||||
|             await jSObject.InvokeAsync<bool>("blazor_downloadFile", url, fileName, new { FileName = path }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
| 		 | ||||
| 		<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>--> | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| 		<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> | ||||
| 		<NoPackageAnalysis>true</NoPackageAnalysis> | ||||
| 		<SignAssembly>false</SignAssembly> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>netstandard2.0;</TargetFrameworks> | ||||
| 		<Version>$(SourceGeneratorVersion)</Version> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| 	<PropertyGroup> | ||||
| 		<Description>工业设备通讯协议-基础类库</Description> | ||||
| 		<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0</TargetFrameworks> | ||||
| 		 | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;net9.0</TargetFrameworks> | ||||
| 		 | ||||
| 		<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>--> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|   | ||||
| @@ -12,13 +12,15 @@ using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public class AsyncReadWriteLock | ||||
| public class AsyncReadWriteLock : IAsyncDisposable | ||||
| { | ||||
|     private readonly int _writeReadRatio = 3; // 写3次会允许1次读,但写入也不会被阻止,具体协议取决于插件协议实现 | ||||
|     public AsyncReadWriteLock(int writeReadRatio) | ||||
|     public AsyncReadWriteLock(int writeReadRatio, bool writePriority) | ||||
|     { | ||||
|         _writeReadRatio = writeReadRatio; | ||||
|         _writePriority = writePriority; | ||||
|     } | ||||
|     private bool _writePriority; | ||||
|     private AsyncAutoResetEvent _readerLock = new AsyncAutoResetEvent(false); // 控制读计数 | ||||
|     private long _writerCount = 0; // 当前活跃的写线程数 | ||||
|     private long _readerCount = 0; // 当前被阻塞的读线程数 | ||||
| @@ -53,12 +55,15 @@ public class AsyncReadWriteLock | ||||
|     { | ||||
|  | ||||
|         if (Interlocked.Increment(ref _writerCount) == 1) | ||||
|         { | ||||
|             if (_writePriority) | ||||
|             { | ||||
|                 var cancellationTokenSource = _cancellationTokenSource; | ||||
|                 _cancellationTokenSource = new(); | ||||
|                 await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取 | ||||
|                 cancellationTokenSource.SafeDispose(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return new Writer(this); | ||||
|     } | ||||
| @@ -102,6 +107,16 @@ public class AsyncReadWriteLock | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async ValueTask DisposeAsync() | ||||
|     { | ||||
|         if (_cancellationTokenSource != null) | ||||
|         { | ||||
|             await _cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); | ||||
|             _cancellationTokenSource.SafeDispose(); | ||||
|         } | ||||
|         _readerLock.SetAll(); | ||||
|     } | ||||
|  | ||||
|     private int _writeSinceLastReadCount = 0; | ||||
|     private struct Writer : IDisposable | ||||
|     { | ||||
|   | ||||
| @@ -18,7 +18,7 @@ namespace BootstrapBlazor.Components; | ||||
| /// <param name="fieldName">字段名称</param> | ||||
| /// <param name="fieldType">字段类型</param> | ||||
| /// <param name="fieldText">显示文字</param> | ||||
| internal sealed class InternalTableColumn(string fieldName, Type fieldType, string? fieldText = null) : IEditorItem, ITableColumn | ||||
| public sealed class DefaultTableColumn(string fieldName, Type fieldType, string? fieldText = null) : IEditorItem, ITableColumn | ||||
| { | ||||
|     public IEnumerable<KeyValuePair<string, object>>? ComponentParameters { get; set; } | ||||
|     public Type? ComponentType { get; set; } | ||||
| @@ -12,7 +12,7 @@ using BootstrapBlazor.Components; | ||||
| 
 | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| 
 | ||||
| public class ExportFilter | ||||
| public class GatewayExportFilter | ||||
| { | ||||
|     public FilterKeyValueAction FilterKeyValueAction { get; set; } | ||||
|     public string? PluginName { get; set; } | ||||
| @@ -10,7 +10,7 @@ | ||||
| 
 | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| 
 | ||||
| public static class ExportString | ||||
| public static class GatewayExportString | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 通道名称 | ||||
| @@ -47,7 +47,7 @@ public static class ExportString | ||||
|         get | ||||
|         { | ||||
|             if (localizer == null) | ||||
|                 localizer = App.CreateLocalizerByType(typeof(ExportString)); | ||||
|                 localizer = App.CreateLocalizerByType(typeof(GatewayExportString)); | ||||
|             return localizer; | ||||
|         } | ||||
|     } | ||||
| @@ -176,7 +176,7 @@ public class ControlController : ControllerBase, IRpcServer | ||||
|     [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, type, restart, default); | ||||
|         return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables, type, restart); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -188,7 +188,7 @@ public class ControlController : ControllerBase, IRpcServer | ||||
|     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(); | ||||
|         return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart, default); | ||||
|         return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -200,7 +200,7 @@ public class ControlController : ControllerBase, IRpcServer | ||||
|     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(); | ||||
|         return GlobalData.DeviceRuntimeService.DeleteDeviceAsync(ids, restart, default); | ||||
|         return GlobalData.DeviceRuntimeService.DeleteDeviceAsync(ids, restart); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -212,7 +212,7 @@ public class ControlController : ControllerBase, IRpcServer | ||||
|     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(); | ||||
|         return GlobalData.VariableRuntimeService.DeleteVariableAsync(ids, restart, default); | ||||
|         return GlobalData.VariableRuntimeService.DeleteVariableAsync(ids, restart); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -223,7 +223,7 @@ public class ControlController : ControllerBase, IRpcServer | ||||
|     [TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)] | ||||
|     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); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -45,7 +45,7 @@ public class GatewayExportController : ControllerBase | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [HttpPost("device")] | ||||
|     public async Task<IActionResult> DownloadDeviceAsync([FromBody] ExportFilter input) | ||||
|     public async Task<IActionResult> DownloadDeviceAsync([FromBody] GatewayExportFilter input) | ||||
|     { | ||||
|         input.QueryPageOptions.IsPage = false; | ||||
|         input.QueryPageOptions.IsVirtualScroll = false; | ||||
| @@ -58,7 +58,7 @@ public class GatewayExportController : ControllerBase | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [HttpPost("channel")] | ||||
|     public async Task<IActionResult> DownloadChannelAsync([FromBody] ExportFilter input) | ||||
|     public async Task<IActionResult> DownloadChannelAsync([FromBody] GatewayExportFilter input) | ||||
|     { | ||||
|         input.QueryPageOptions.IsPage = false; | ||||
|         input.QueryPageOptions.IsVirtualScroll = false; | ||||
| @@ -72,7 +72,7 @@ public class GatewayExportController : ControllerBase | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [HttpPost("variable")] | ||||
|     public async Task<IActionResult> DownloadVariableAsync([FromBody] ExportFilter input) | ||||
|     public async Task<IActionResult> DownloadVariableAsync([FromBody] GatewayExportFilter input) | ||||
|     { | ||||
|         input.QueryPageOptions.IsPage = false; | ||||
|         input.QueryPageOptions.IsVirtualScroll = false; | ||||
|   | ||||
| @@ -57,6 +57,9 @@ public abstract class BusinessBase : DriverBase | ||||
|     /// </summary> | ||||
|     protected Dictionary<string, List<VariableRuntime>> VariableRuntimeGroups { get; set; } = new(); | ||||
|  | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|     public override Task AfterVariablesChangedAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         LogMessage?.LogInformation("Refresh variable"); | ||||
| @@ -117,6 +120,8 @@ public abstract class BusinessBase : DriverBase | ||||
|             }; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 间隔执行 | ||||
|     /// </summary> | ||||
| @@ -134,4 +139,5 @@ public abstract class BusinessBase : DriverBase | ||||
|             CurrentDevice?.SetDeviceStatus(TimerX.Now, true); | ||||
|         } | ||||
|     } | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,12 @@ namespace ThingsGateway.Gateway.Application; | ||||
| /// </summary> | ||||
| public abstract class BusinessBaseWithCache : BusinessBase | ||||
| { | ||||
|  | ||||
|     protected sealed override BusinessPropertyBase _businessPropertyBase => _businessPropertyWithCache; | ||||
|  | ||||
|     protected abstract BusinessPropertyWithCache _businessPropertyWithCache { get; } | ||||
|  | ||||
| #if !Management | ||||
|     #region 条件 | ||||
|  | ||||
|     protected abstract bool AlarmModelEnable { get; } | ||||
| @@ -537,9 +543,7 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|     private CacheDB DBCacheVar; | ||||
|     private CacheDB DBCacheVars; | ||||
|  | ||||
|     protected sealed override BusinessPropertyBase _businessPropertyBase => _businessPropertyWithCache; | ||||
|  | ||||
|     protected abstract BusinessPropertyWithCache _businessPropertyWithCache { get; } | ||||
|  | ||||
|     protected object cacheLock = new(); | ||||
|  | ||||
| @@ -976,4 +980,6 @@ public abstract class BusinessBaseWithCache : BusinessBase | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -19,6 +19,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| /// </summary> | ||||
| public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache | ||||
| { | ||||
| #if !Management | ||||
|     protected override bool AlarmModelEnable => true; | ||||
|  | ||||
|     protected override bool DevModelEnable => false; | ||||
| @@ -104,4 +105,6 @@ public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -29,6 +29,9 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | ||||
|     /// </summary> | ||||
|     protected abstract BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval { get; } | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|  | ||||
|     protected internal override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken) | ||||
|     { | ||||
|         if (AlarmModelEnable) | ||||
| @@ -340,5 +343,5 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,8 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|  | ||||
|     protected abstract BusinessPropertyWithCacheIntervalScript _businessPropertyWithCacheIntervalScript { get; } | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|     public virtual string[] Match(string input) | ||||
|     { | ||||
|         // 生成缓存键,以确保缓存的唯一性 | ||||
| @@ -347,4 +349,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase | ||||
|     [GeneratedRegex(@"\$\{(.+?)\}")] | ||||
|     private static partial Regex TopicRegex(); | ||||
|     #endregion 封装方法 | ||||
|  | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -15,9 +15,12 @@ namespace ThingsGateway.Gateway.Application; | ||||
| /// </summary> | ||||
| public abstract partial class BusinessBaseWithCacheIntervalScriptAll : BusinessBaseWithCacheIntervalScript | ||||
| { | ||||
| #if !Management | ||||
|     protected override bool AlarmModelEnable => true; | ||||
|  | ||||
|     protected override bool DevModelEnable => true; | ||||
|  | ||||
|     protected override bool VarModelEnable => true; | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| /// </summary> | ||||
| public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCacheInterval | ||||
| { | ||||
| #if !Management | ||||
|     protected override bool AlarmModelEnable => false; | ||||
|  | ||||
|     protected override bool DevModelEnable => false; | ||||
| @@ -29,5 +30,5 @@ public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCa | ||||
|     { | ||||
|         throw new NotImplementedException(); | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -18,6 +18,7 @@ namespace ThingsGateway.Gateway.Application; | ||||
| /// </summary> | ||||
| public static class BusinessDatabaseUtil | ||||
| { | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取数据库链接 | ||||
|     /// </summary> | ||||
| @@ -45,6 +46,7 @@ public static class BusinessDatabaseUtil | ||||
|         return sqlSugarClient; | ||||
|     } | ||||
|  | ||||
| #if !Management | ||||
|     /// <summary> | ||||
|     /// 按条件获取DB插件中的全部历史报警(不分页) | ||||
|     /// </summary> | ||||
| @@ -144,4 +146,6 @@ public static class BusinessDatabaseUtil | ||||
|             return new("GetDBHistoryValues Fail", ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,9 @@ using System.Collections.Concurrent; | ||||
|  | ||||
| using ThingsGateway.Common.Extension; | ||||
| using ThingsGateway.Extension.Generic; | ||||
| #if !Management | ||||
| using ThingsGateway.Gateway.Application.Extensions; | ||||
| #endif | ||||
| using ThingsGateway.NewLife.Json.Extension; | ||||
| using ThingsGateway.NewLife.Threading; | ||||
|  | ||||
| @@ -29,20 +31,31 @@ namespace ThingsGateway.Gateway.Application; | ||||
| /// 采集插件,继承实现不同PLC通讯 | ||||
| /// <para></para> | ||||
| /// </summary> | ||||
| public abstract partial class CollectBase : DriverBase, IRpcDriver | ||||
| public abstract partial class CollectBase : DriverBase | ||||
| #if !Management | ||||
|     , IRpcDriver | ||||
| #endif | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 插件配置项 | ||||
|     /// </summary> | ||||
|     public abstract CollectPropertyBase CollectProperties { get; } | ||||
|  | ||||
|  | ||||
|     public sealed override object DriverProperties => CollectProperties; | ||||
|  | ||||
|     public virtual string GetAddressDescription() | ||||
|     { | ||||
|         return string.Empty; | ||||
|     } | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 特殊方法 | ||||
|     /// </summary> | ||||
|     public List<DriverMethodInfo>? DriverMethodInfos { get; private set; } | ||||
|  | ||||
|     public sealed override object DriverProperties => CollectProperties; | ||||
|  | ||||
|     public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         LogMessage?.LogInformation("Refresh variable"); | ||||
| @@ -195,13 +208,10 @@ public abstract partial class CollectBase : DriverBase, IRpcDriver | ||||
|         // 从插件服务中获取当前设备关联的驱动方法信息列表 | ||||
|         DriverMethodInfos = GlobalData.PluginService.GetDriverMethodInfos(device.PluginName, this); | ||||
|  | ||||
|         ReadWriteLock = new(CollectProperties.DutyCycle); | ||||
|         ReadWriteLock = new(CollectProperties.DutyCycle, CollectProperties.WritePriority); | ||||
|     } | ||||
|  | ||||
|     public virtual string GetAddressDescription() | ||||
|     { | ||||
|         return string.Empty; | ||||
|     } | ||||
|  | ||||
|     protected virtual bool VariableSourceReadsEnable => true; | ||||
|  | ||||
|     protected List<IScheduledTask> VariableTasks = new List<IScheduledTask>(); | ||||
| @@ -468,6 +478,7 @@ public abstract partial class CollectBase : DriverBase, IRpcDriver | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 连读打包,返回实际通讯包信息<see cref="VariableSourceRead"/> | ||||
|     /// <br></br>每个驱动打包方法不一样,所以需要实现这个接口 | ||||
| @@ -723,9 +734,13 @@ public abstract partial class CollectBase : DriverBase, IRpcDriver | ||||
|  | ||||
|     #endregion 写入方法 | ||||
|  | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|         _linkedCtsCache?.SafeDispose(); | ||||
|         return base.DisposeAsync(disposing); | ||||
|         if (ReadWriteLock != null) | ||||
|             await ReadWriteLock.SafeDisposeAsync().ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -33,13 +33,7 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|     /// </summary> | ||||
|     public virtual IDevice? FoundationDevice { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否连接成功 | ||||
|     /// </summary> | ||||
|     public override bool IsConnected() | ||||
|     { | ||||
|         return FoundationDevice?.OnLine == true; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public override string ToString() | ||||
|     { | ||||
| @@ -53,6 +47,24 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|             await FoundationDevice.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     public override string GetAddressDescription() | ||||
|     { | ||||
|         return FoundationDevice?.GetAddressDescription(); | ||||
|     } | ||||
| #if !Management | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否连接成功 | ||||
|     /// </summary> | ||||
|     public override bool IsConnected() | ||||
|     { | ||||
|         return FoundationDevice?.OnLine == true; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 开始通讯执行的方法 | ||||
|     /// </summary> | ||||
| @@ -66,10 +78,6 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override string GetAddressDescription() | ||||
|     { | ||||
|         return FoundationDevice?.GetAddressDescription(); | ||||
|     } | ||||
|  | ||||
|     protected override async Task TestOnline(object? state, CancellationToken cancellationToken) | ||||
|     { | ||||
| @@ -216,4 +224,7 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|         // 返回包含操作结果的字典 | ||||
|         return new Dictionary<string, OperResult>(operResults); | ||||
|     } | ||||
|  | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -37,6 +37,12 @@ public abstract class CollectPropertyBase : DriverPropertyBase | ||||
|     /// </summary> | ||||
|     [MinValue(1)] | ||||
|     public virtual int DutyCycle { get; set; } = 3; | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 写优先,写入时强制读取消操作 | ||||
|     /// </summary> | ||||
|     public virtual bool WritePriority { get; set; } = false; | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| @@ -56,4 +62,6 @@ public abstract class CollectPropertyRetryBase : CollectPropertyBase | ||||
|     [MinValue(1)] | ||||
|     public override int DutyCycle { get; set; } = 3; | ||||
|  | ||||
|     [DynamicProperty(Remark = "写优先,写入时强制读取消操作")] | ||||
|     public override bool WritePriority { get; set; } = false; | ||||
| } | ||||
| @@ -37,20 +37,6 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver | ||||
|  | ||||
|     #region 属性 | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备 | ||||
|     /// </summary> | ||||
|     public DeviceRuntime? CurrentDevice { get; private set; } | ||||
|     /// <summary> | ||||
|     /// 当前设备Id | ||||
|     /// </summary> | ||||
|     public long DeviceId => CurrentDevice?.Id ?? 0; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备名称 | ||||
|     /// </summary> | ||||
|     public string? DeviceName => CurrentDevice?.Name; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 调试UI Type,如果不存在,返回null | ||||
|     /// </summary> | ||||
| @@ -76,25 +62,8 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver | ||||
|     /// </summary> | ||||
|     public abstract object DriverProperties { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否执行了Start方法 | ||||
|     /// </summary> | ||||
|     public bool IsStarted { get; protected set; } = false; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否初始化成功,失败时不再执行,等待检测重启 | ||||
|     /// </summary> | ||||
|     public bool IsInitSuccess { get; internal set; } = true; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否采集插件 | ||||
|     /// </summary> | ||||
|     public virtual bool? IsCollectDevice => CurrentDevice?.IsCollect; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 暂停 | ||||
|     /// </summary> | ||||
|     public bool Pause => CurrentDevice?.Pause == true; | ||||
|  | ||||
|     private List<IEditorItem> pluginPropertyEditorItems; | ||||
|     public List<IEditorItem> PluginPropertyEditorItems | ||||
| @@ -112,6 +81,82 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver | ||||
|     private IStringLocalizer Localizer { get; } | ||||
|  | ||||
|     #endregion 属性 | ||||
|  | ||||
|  | ||||
|     public virtual bool GetAuthentication(out DateTime? expireTime) | ||||
|     { | ||||
|         expireTime = null; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     public string GetAuthString() | ||||
|     { | ||||
|         if (PluginServiceUtil.IsEducation(GetType())) | ||||
|         { | ||||
|             StringBuilder stringBuilder = new(); | ||||
|             var ret = GetAuthentication(out var expireTime); | ||||
|             if (ret) | ||||
|             { | ||||
|                 stringBuilder.Append(Localizer["Authorized"]); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 stringBuilder.Append(Localizer["Unauthorized"]); | ||||
|             } | ||||
|  | ||||
|             stringBuilder.Append("   "); | ||||
|             if (expireTime.HasValue && (DateTime.Now - expireTime.Value).TotalHours > -72) | ||||
|             { | ||||
|                 stringBuilder.Append(','); | ||||
|                 stringBuilder.Append(Localizer["ExpireTime", expireTime.Value.ToString("yyyy-MM-dd HH")]); | ||||
|             } | ||||
|  | ||||
|             return stringBuilder.ToString(); | ||||
|         } | ||||
|         return string.Empty; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否执行了Start方法 | ||||
|     /// </summary> | ||||
|     public bool IsStarted { get; protected set; } = false; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否初始化成功,失败时不再执行,等待检测重启 | ||||
|     /// </summary> | ||||
|     public bool IsInitSuccess { get; internal set; } = true; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否采集插件 | ||||
|     /// </summary> | ||||
|     public virtual bool? IsCollectDevice => CurrentDevice?.IsCollect; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备 | ||||
|     /// </summary> | ||||
|     public DeviceRuntime? CurrentDevice { get; private set; } | ||||
|     /// <summary> | ||||
|     /// 当前设备Id | ||||
|     /// </summary> | ||||
|     public long DeviceId => CurrentDevice?.Id ?? 0; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前设备名称 | ||||
|     /// </summary> | ||||
|     public string? DeviceName => CurrentDevice?.Name; | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 暂停 | ||||
|     /// </summary> | ||||
|     public bool Pause => CurrentDevice?.Pause == true; | ||||
|  | ||||
|     protected object pauseLock = new object(); | ||||
|     /// <summary> | ||||
|     /// 暂停 | ||||
| @@ -378,38 +423,6 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver | ||||
|  | ||||
|     #region 插件重写 | ||||
|  | ||||
|     public virtual bool GetAuthentication(out DateTime? expireTime) | ||||
|     { | ||||
|         expireTime = null; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     public string GetAuthString() | ||||
|     { | ||||
|         if (PluginServiceUtil.IsEducation(GetType())) | ||||
|         { | ||||
|             StringBuilder stringBuilder = new(); | ||||
|             var ret = GetAuthentication(out var expireTime); | ||||
|             if (ret) | ||||
|             { | ||||
|                 stringBuilder.Append(Localizer["Authorized"]); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 stringBuilder.Append(Localizer["Unauthorized"]); | ||||
|             } | ||||
|  | ||||
|             stringBuilder.Append("   "); | ||||
|             if (expireTime.HasValue && (DateTime.Now - expireTime.Value).TotalHours > -72) | ||||
|             { | ||||
|                 stringBuilder.Append(','); | ||||
|                 stringBuilder.Append(Localizer["ExpireTime", expireTime.Value.ToString("yyyy-MM-dd HH")]); | ||||
|             } | ||||
|  | ||||
|             return stringBuilder.ToString(); | ||||
|         } | ||||
|         return string.Empty; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 开始通讯执行的方法 | ||||
| @@ -456,4 +469,7 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver | ||||
|     public abstract Task AfterVariablesChangedAsync(CancellationToken cancellationToken); | ||||
|  | ||||
|     #endregion 插件重写 | ||||
|  | ||||
|  | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -37,6 +37,7 @@ public static class DynamicModelExtension | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #if !Management | ||||
|     /// <summary> | ||||
|     /// 获取变量的业务属性值 | ||||
|     /// </summary> | ||||
| @@ -79,6 +80,8 @@ public static class DynamicModelExtension | ||||
|         return null; // 未找到对应的业务设备Id,返回null | ||||
|     } | ||||
|  | ||||
| #endif | ||||
|  | ||||
|     public static IEnumerable<IGrouping<object[], T>> GroupByKeys<T>(this IEnumerable<T> values, params string[] keys) | ||||
|     { | ||||
|         // 获取动态对象集合中指定键的属性信息 | ||||
|   | ||||
| @@ -16,25 +16,31 @@ namespace ThingsGateway.Gateway.Application | ||||
| { | ||||
|     public interface IDriver : IAsyncDisposable | ||||
|     { | ||||
|         bool DisposedValue { get; } | ||||
|         ChannelRuntime CurrentChannel { get; } | ||||
|         DeviceRuntime? CurrentDevice { get; } | ||||
|         long DeviceId { get; } | ||||
|         string? DeviceName { get; } | ||||
|  | ||||
|         Type DriverDebugUIType { get; } | ||||
|         object DriverProperties { get; } | ||||
|  | ||||
|         Type DriverPropertyUIType { get; } | ||||
|         Type DriverUIType { get; } | ||||
|         Type DriverVariableAddressUIType { get; } | ||||
|         List<IEditorItem> PluginPropertyEditorItems { get; } | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|         bool? IsCollectDevice { get; } | ||||
|         bool DisposedValue { get; } | ||||
|         ChannelRuntime CurrentChannel { get; } | ||||
|         DeviceRuntime? CurrentDevice { get; } | ||||
|         long DeviceId { get; } | ||||
|         string? DeviceName { get; } | ||||
|  | ||||
|  | ||||
|         bool IsInitSuccess { get; } | ||||
|         bool IsStarted { get; } | ||||
|         bool Pause { get; } | ||||
|         LoggerGroup LogMessage { get; } | ||||
|         string LogPath { get; } | ||||
|         bool Pause { get; } | ||||
|         string PluginDirectory { get; } | ||||
|         List<IEditorItem> PluginPropertyEditorItems { get; } | ||||
|         Dictionary<long, VariableRuntime> IdVariableRuntimes { get; } | ||||
|         IDeviceThreadManage DeviceThreadManage { get; } | ||||
|  | ||||
| @@ -42,6 +48,10 @@ namespace ThingsGateway.Gateway.Application | ||||
|         void PauseThread(bool pause); | ||||
|         Task SetLogAsync(LogLevel? logLevel = null, bool upDataBase = true); | ||||
|         Task AfterVariablesChangedAsync(CancellationToken cancellationToken); | ||||
|  | ||||
|  | ||||
| #endif | ||||
|  | ||||
|         string GetAuthString(); | ||||
|         bool GetAuthentication(out DateTime? expireTime); | ||||
|     } | ||||
|   | ||||
| @@ -17,7 +17,6 @@ using TouchSocket.Core; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| #pragma warning disable CS0649 | ||||
|  | ||||
| /// <summary> | ||||
| /// 通道表 | ||||
|   | ||||
| @@ -17,7 +17,6 @@ using System.ComponentModel.DataAnnotations; | ||||
| using ThingsGateway.NewLife.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| #pragma warning disable CS0649 | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备表 | ||||
| @@ -172,6 +171,7 @@ public class Device : BaseDataEntity, IValidatableObject | ||||
|  | ||||
|     #endregion 备用字段 | ||||
|  | ||||
| #if !Management | ||||
|     /// <summary> | ||||
|     /// 导入验证专用 | ||||
|     /// </summary> | ||||
| @@ -179,6 +179,9 @@ public class Device : BaseDataEntity, IValidatableObject | ||||
|     [Newtonsoft.Json.JsonIgnore] | ||||
|     internal bool IsUp; | ||||
|  | ||||
|  | ||||
| #endif | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 额外属性 | ||||
|     /// </summary> | ||||
| @@ -186,6 +189,7 @@ public class Device : BaseDataEntity, IValidatableObject | ||||
|     [Newtonsoft.Json.JsonIgnore] | ||||
|     [MapperIgnore] | ||||
|     public ModelValueValidateForm? ModelValueValidateForm; | ||||
|  | ||||
|     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) | ||||
|     { | ||||
|         if (RedundantEnable && RedundantDeviceId == null) | ||||
|   | ||||
| @@ -16,7 +16,6 @@ using System.Collections.Concurrent; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| #pragma warning disable CS0649 | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备变量表 | ||||
| @@ -81,6 +80,8 @@ public class Variable : BaseDataEntity, IValidatableObject | ||||
|     private string remark4; | ||||
|     private string remark5; | ||||
|  | ||||
|     [System.Text.Json.Serialization.JsonIgnore] | ||||
|     [Newtonsoft.Json.JsonIgnore] | ||||
|     [MapperIgnore] | ||||
|     public ValidateForm AlarmPropertysValidateForm; | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| { | ||||
|   "ThingsGateway.Management.Application.ExportString": { | ||||
|   "ThingsGateway.Management.Application.ManagementExportString": { | ||||
|  | ||||
|     "ManagementConfigName": "ManagementConfigName" | ||||
|   }, | ||||
| @@ -321,7 +321,8 @@ | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.CollectPropertyRetryBase": { | ||||
|     "RetryCount": "RetryCount", | ||||
|     "DutyCycle": "DutyCycle" | ||||
|     "DutyCycle": "DutyCycle", | ||||
|     "WritePriority": "WritePriority" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.ControlController": { | ||||
|     "BatchSaveChannelAsync": "BatchSaveChannel", | ||||
| @@ -340,6 +341,7 @@ | ||||
|     "WriteVariablesAsync": "Write variables" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.Device": { | ||||
|     "Pause": "Pause", | ||||
|     "ChannelError": "Channel error", | ||||
|     "ChannelId": "Channel", | ||||
|     "ChannelId.MinValue": "{0} cannot be empty", | ||||
| @@ -396,7 +398,7 @@ | ||||
|     "ExpireTime": "ExpireTime {0}", | ||||
|     "Unauthorized": "Unauthorized" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.ExportString": { | ||||
|   "ThingsGateway.Gateway.Application.GatewayExportString": { | ||||
|     "BusinessDeviceName": "BusinessDevice", | ||||
|     "ChannelName": "Channel", | ||||
|     "DeviceName": "Device", | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| { | ||||
|   "ThingsGateway.Management.Application.ExportString": { | ||||
|   "ThingsGateway.Management.Application.ManagementExportString": { | ||||
|  | ||||
|     "ManagementConfigName": "通讯配置" | ||||
|   }, | ||||
| @@ -323,7 +323,8 @@ | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.CollectPropertyRetryBase": { | ||||
|     "RetryCount": "失败重试次数", | ||||
|     "DutyCycle": "占空比" | ||||
|     "DutyCycle": "占空比", | ||||
|     "WritePriority": "写优先" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.ControlController": { | ||||
|     "BatchSaveChannelAsync": "保存通道", | ||||
| @@ -342,6 +343,7 @@ | ||||
|     "WriteVariablesAsync": "写入变量" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.Device": { | ||||
|     "Pause": "暂停", | ||||
|     "ChannelError": "通道错误", | ||||
|     "ChannelId": "通道", | ||||
|     "ChannelId.MinValue": " {0} 不可为空", | ||||
| @@ -400,7 +402,7 @@ | ||||
|     "ExpireTime": "过期时间 {0}", | ||||
|     "Unauthorized": "未授权" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.ExportString": { | ||||
|   "ThingsGateway.Gateway.Application.GatewayExportString": { | ||||
|     "BusinessDeviceName": "业务设备", | ||||
|     "ChannelName": "通道", | ||||
|     "DeviceName": "设备", | ||||
|   | ||||
| @@ -93,6 +93,7 @@ public class ChannelRuntime : Channel | ||||
|     [Newtonsoft.Json.JsonIgnore] | ||||
|     public int? DeviceRuntimeCount => DeviceRuntimes?.Count; | ||||
|  | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public bool Started => DeviceThreadManage != null; | ||||
| #else | ||||
|  | ||||
| @@ -107,6 +108,7 @@ public class ChannelRuntime : Channel | ||||
|     /// </summary> | ||||
|     public int? DeviceRuntimeCount { get; set; } | ||||
|  | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public bool Started { get; set; } | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -39,6 +39,20 @@ public class DeviceRuntime : Device | ||||
|     /// </summary> | ||||
|     public DateTime ActiveTime { get; set; } = DateTime.UnixEpoch.ToLocalTime(); | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否采集 | ||||
|     /// </summary> | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public bool? IsCollect => PluginType == null ? null : PluginType == PluginTypeEnum.Collect; | ||||
|  | ||||
|  | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public string LogPath => Name.GetDeviceLogPath(); | ||||
|  | ||||
| #if !Management | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件名称 | ||||
|     /// </summary> | ||||
| @@ -50,12 +64,11 @@ public class DeviceRuntime : Device | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual PluginTypeEnum? PluginType => ChannelRuntime?.PluginInfo?.PluginType; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否采集 | ||||
|     /// </summary> | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public bool? IsCollect => PluginType == null ? null : PluginType == PluginTypeEnum.Collect; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通道名称 | ||||
|     /// </summary> | ||||
|     public string? ChannelName => ChannelRuntime?.Name; | ||||
|     /// <summary> | ||||
|     /// 通道 | ||||
|     /// </summary> | ||||
| @@ -65,15 +78,9 @@ public class DeviceRuntime : Device | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public ChannelRuntime? ChannelRuntime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通道名称 | ||||
|     /// </summary> | ||||
|     public string? ChannelName => ChannelRuntime?.Name; | ||||
|  | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public string LogPath => Name.GetDeviceLogPath(); | ||||
|  | ||||
|  | ||||
| #if !Management | ||||
|     public bool Started => Driver?.DeviceThreadManage != null; | ||||
|  | ||||
|     [System.Text.Json.Serialization.JsonIgnore] | ||||
|     [Newtonsoft.Json.JsonIgnore] | ||||
| @@ -122,6 +129,27 @@ public class DeviceRuntime : Device | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件名称 | ||||
|     /// </summary> | ||||
|     public virtual string PluginName { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 插件名称 | ||||
|     /// </summary> | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public virtual PluginTypeEnum? PluginType { get; set; } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 通道名称 | ||||
|     /// </summary> | ||||
|     public string? ChannelName { get; set; } | ||||
|  | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public bool Started { get; set; } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备状态 | ||||
|     /// </summary> | ||||
| @@ -175,8 +203,13 @@ public class DeviceRuntime : Device | ||||
|         } | ||||
|         set | ||||
|         { | ||||
| #if !Management | ||||
|             if (!value.IsNullOrWhiteSpace()) | ||||
|                 _lastErrorMessage = TimerX.Now.ToDefaultDateTimeFormat() + " - " + value; | ||||
| #else | ||||
|             if (!value.IsNullOrWhiteSpace()) | ||||
|                 _lastErrorMessage = value; | ||||
| #endif | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -32,7 +32,7 @@ public partial class VariableRuntime : Variable | ||||
|     IDisposable | ||||
| #endif | ||||
| { | ||||
|     [AutoGenerateColumn(Visible = false)] | ||||
|     [AutoGenerateColumn(Ignore = true)] | ||||
|     public bool ValueInited { get => _valueInited; set => _valueInited = value; } | ||||
|  | ||||
|     #region 属性 | ||||
| @@ -112,6 +112,11 @@ public partial class VariableRuntime : Variable | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
| #if !Management | ||||
|     private bool _isOnline; | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否在线 | ||||
|     /// </summary> | ||||
| @@ -136,7 +141,6 @@ public partial class VariableRuntime : Variable | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #if !Management | ||||
|     /// <summary> | ||||
|     /// 设备名称 | ||||
|     /// </summary> | ||||
| @@ -167,6 +171,14 @@ public partial class VariableRuntime : Variable | ||||
|  | ||||
| #else | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否在线 | ||||
|     /// </summary> | ||||
|     [AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)] | ||||
|     public bool IsOnline { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备名称 | ||||
|     /// </summary> | ||||
| @@ -221,8 +233,9 @@ public partial class VariableRuntime : Variable | ||||
|  | ||||
|     private DateTime collectTime = DateTime.UnixEpoch.ToLocalTime(); | ||||
|  | ||||
|     private bool _isOnline; | ||||
| #pragma warning disable CS0414 | ||||
|     private bool _isOnlineChanged; | ||||
| #pragma warning restore CS0414 | ||||
|     private bool _valueInited; | ||||
|  | ||||
|     private object _value; | ||||
|   | ||||
| @@ -13,7 +13,9 @@ using BootstrapBlazor.Components; | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Extension.Generic; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| @@ -26,6 +28,50 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|     } | ||||
|     private WaitLock WaitLock { get; set; } = new WaitLock(nameof(ChannelRuntimeService)); | ||||
|  | ||||
|     public Task<string> GetChannelNameAsync(long channelId) | ||||
|     { | ||||
|         return Task.FromResult(GlobalData.ReadOnlyIdChannels.TryGetValue(channelId, out var channelRuntime) ? channelRuntime.Name : string.Empty); | ||||
|     } | ||||
|  | ||||
|     public Task<QueryData<ChannelRuntime>> OnChannelQueryAsync(QueryPageOptions options) | ||||
|     { | ||||
|         var data = GlobalData.IdChannels.Select(a => a.Value) | ||||
|         .WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText)) | ||||
|         .GetQueryData(options); | ||||
|         return Task.FromResult(data); | ||||
|     } | ||||
|  | ||||
|     public Task<List<Channel>> GetChannelListAsync(QueryPageOptions options, int max = 0) | ||||
|     { | ||||
|         var models = GlobalData.IdChannels.Select(a => a.Value) | ||||
|         .WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText)) | ||||
|         .GetData(options, out var total).Cast<Channel>().ToList(); | ||||
|  | ||||
|         if (max > 0 && models.Count > max) | ||||
|         { | ||||
|             throw new("online Excel max data count 2000"); | ||||
|         } | ||||
|         return Task.FromResult(models); | ||||
|     } | ||||
|  | ||||
|     public Task<USheetDatas> ExportChannelAsync(List<Channel> channels) | ||||
|     { | ||||
|         return Task.FromResult(ChannelServiceHelpers.ExportChannel(channels)); | ||||
|     } | ||||
|     public Task<string> GetPluginNameAsync(long channelId) | ||||
|     { | ||||
|         var pluginName = GlobalData.ReadOnlyIdChannels.TryGetValue(channelId, out var channel) ? channel.PluginName : string.Empty; | ||||
|         return Task.FromResult(pluginName); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public async Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option) | ||||
|     { | ||||
|         var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false); | ||||
|         var _channelItems = channels.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(option.SearchText)).GetQueryData(option, GatewayResourceUtil.BuildChannelSelectList); | ||||
|         return _channelItems; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id) | ||||
|     { | ||||
| @@ -97,17 +143,17 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|             channels.Add(channel); | ||||
|         } | ||||
|  | ||||
|         await GlobalData.ChannelRuntimeService.CopyAsync(channels, devices, AutoRestartThread, default).ConfigureAwait(false); | ||||
|         await GlobalData.ChannelRuntimeService.CopyAsync(channels, devices, AutoRestartThread).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     public async Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken) | ||||
|     public async Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var result = await GlobalData.ChannelService.CopyAsync(models, devices).ConfigureAwait(false); | ||||
|             var ids = models.Select(a => a.Id).ToHashSet(); | ||||
| @@ -124,7 +170,7 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|             { | ||||
|                 await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false); | ||||
|  | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(_logger, cancellationToken).ConfigureAwait(false); | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
| @@ -135,11 +181,11 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken) | ||||
|     public async Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var result = await GlobalData.ChannelService.InsertAsync(models, devices, variables).ConfigureAwait(false); | ||||
|             var ids = models.Select(a => a.Id).ToHashSet(); | ||||
| @@ -156,7 +202,7 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|             { | ||||
|                 await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false); | ||||
|  | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(_logger, cancellationToken).ConfigureAwait(false); | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
| @@ -167,11 +213,11 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken) | ||||
|     public async Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var result = await GlobalData.ChannelService.UpdateAsync(models, devices, variables).ConfigureAwait(false); | ||||
|             var ids = models.Select(a => a.Id).ToHashSet(); | ||||
| @@ -188,7 +234,7 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|             { | ||||
|                 await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false); | ||||
|  | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(_logger, cancellationToken).ConfigureAwait(false); | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
| @@ -199,7 +245,7 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart) | ||||
|     public async Task<bool> BatchEditChannelAsync(List<Channel> models, Channel oldModel, Channel model, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -225,11 +271,11 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<bool> DeleteChannelAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken) | ||||
|     public async Task<bool> DeleteChannelAsync(List<long> ids, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var array = ids.ToArray(); | ||||
|             var result = await GlobalData.ChannelService.DeleteChannelAsync(array).ConfigureAwait(false); | ||||
| @@ -241,7 +287,7 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|             { | ||||
|                 await GlobalData.ChannelThreadManage.RemoveChannelAsync(array).ConfigureAwait(false); | ||||
|  | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(changedDriver, _logger, cancellationToken).ConfigureAwait(false); | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(changedDriver, _logger).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
| @@ -251,14 +297,105 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Task<bool> ClearChannelAsync(bool restart) | ||||
|     { | ||||
|         return DeleteChannelAsync(GlobalData.IdChannels.Keys.ToList(), restart); | ||||
|     } | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile) => GlobalData.ChannelService.PreviewAsync(browserFile); | ||||
|  | ||||
|     public Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter) => GlobalData.ChannelService.ExportChannelAsync(exportFilter); | ||||
|     public Task<Dictionary<string, object>> ExportChannelAsync(GatewayExportFilter exportFilter) => GlobalData.ChannelService.ExportChannelAsync(exportFilter); | ||||
|  | ||||
|     public Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> data) => | ||||
|       GlobalData.ChannelService.ExportMemoryStream(data); | ||||
|  | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync(USheetDatas input, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|  | ||||
|             var data = await ChannelServiceHelpers.ImportAsync(input).ConfigureAwait(false); | ||||
|  | ||||
|             if (data.Any(a => a.Value.HasError)) return data; | ||||
|  | ||||
|             var result = await GlobalData.ChannelService.ImportChannelAsync(data).ConfigureAwait(false); | ||||
|  | ||||
|             var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false); | ||||
|  | ||||
|             RuntimeServiceHelper.Init(newChannelRuntimes); | ||||
|  | ||||
|             //根据条件重启通道线程 | ||||
|             if (restart) | ||||
|                 await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false); | ||||
|  | ||||
|             return data; | ||||
|         } | ||||
|  | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelFileAsync(string filePath, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var data = await GlobalData.ChannelService.PreviewAsync(filePath).ConfigureAwait(false); | ||||
|  | ||||
|             if (data.Any(a => a.Value.HasError)) return data; | ||||
|             var result = await GlobalData.ChannelService.ImportChannelAsync(data).ConfigureAwait(false); | ||||
|  | ||||
|             var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false); | ||||
|  | ||||
|             RuntimeServiceHelper.Init(newChannelRuntimes); | ||||
|  | ||||
|             //根据条件重启通道线程 | ||||
|             if (restart) | ||||
|                 await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false); | ||||
|  | ||||
|             return data; | ||||
|         } | ||||
|  | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IBrowserFile file, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var data = await GlobalData.ChannelService.PreviewAsync(file).ConfigureAwait(false); | ||||
|  | ||||
|             if (data.Any(a => a.Value.HasError)) return data; | ||||
|             var result = await GlobalData.ChannelService.ImportChannelAsync(data).ConfigureAwait(false); | ||||
|  | ||||
|             var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false); | ||||
|  | ||||
|             RuntimeServiceHelper.Init(newChannelRuntimes); | ||||
|  | ||||
|             //根据条件重启通道线程 | ||||
|             if (restart) | ||||
|                 await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false); | ||||
|  | ||||
|             return data; | ||||
|         } | ||||
|  | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public async Task ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart) | ||||
|     { | ||||
|         try | ||||
| @@ -289,7 +426,7 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|         { | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var result = await GlobalData.ChannelService.ImportAsync(upData, insertData).ConfigureAwait(false); | ||||
|             var result = await GlobalData.ChannelService.ImportChannelAsync(upData, insertData).ConfigureAwait(false); | ||||
|  | ||||
|             var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false); | ||||
|  | ||||
| @@ -381,4 +518,11 @@ public class ChannelRuntimeService : IChannelRuntimeService | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var sheets = await GlobalData.ChannelService.ExportChannelAsync(exportFilter).ConfigureAwait(false); | ||||
|         return await App.GetService<IImportExportService>().CreateFileAsync<Channel>(sheets, "Channel", false).ConfigureAwait(false); | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -247,7 +247,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService | ||||
|     /// 报表查询 | ||||
|     /// </summary> | ||||
|     /// <param name="exportFilter">查询条件</param> | ||||
|     public async Task<QueryData<Channel>> PageAsync(ExportFilter exportFilter) | ||||
|     public async Task<QueryData<Channel>> PageAsync(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false); | ||||
|  | ||||
| @@ -255,7 +255,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService | ||||
|        , exportFilter.FilterKeyValueAction).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     private async Task<Func<ISugarQueryable<Channel>, ISugarQueryable<Channel>>> GetWhereQueryFunc(ExportFilter exportFilter) | ||||
|     private async Task<Func<ISugarQueryable<Channel>, ISugarQueryable<Channel>>> GetWhereQueryFunc(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         HashSet<long>? channel = null; | ||||
|         if (exportFilter.PluginType != null) | ||||
| @@ -331,15 +331,15 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("ExportChannel", isRecordPar: false, localizerType: typeof(Channel))] | ||||
|     public async Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter) | ||||
|     public async Task<Dictionary<string, object>> ExportChannelAsync(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var channels = await GetEnumerableData(exportFilter).ConfigureAwait(false); | ||||
|         var rows = ChannelServiceHelpers.ExportRows(channels); // IEnumerable 延迟执行 | ||||
|         var sheets = ChannelServiceHelpers.WrapAsSheet(ExportString.ChannelName, rows); | ||||
|         var sheets = ChannelServiceHelpers.WrapAsSheet(GatewayExportString.ChannelName, rows); | ||||
|         return sheets; | ||||
|     } | ||||
|  | ||||
|     private async Task<IAsyncEnumerable<Channel>> GetEnumerableData(ExportFilter exportFilter) | ||||
|     private async Task<IAsyncEnumerable<Channel>> GetEnumerableData(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var db = GetDB(); | ||||
|         var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false); | ||||
| @@ -354,7 +354,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService | ||||
|     public async Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> channels) | ||||
|     { | ||||
|         var rows = ChannelServiceHelpers.ExportRows(channels); // IEnumerable 延迟执行 | ||||
|         var sheets = ChannelServiceHelpers.WrapAsSheet(ExportString.ChannelName, rows); | ||||
|         var sheets = ChannelServiceHelpers.WrapAsSheet(GatewayExportString.ChannelName, rows); | ||||
|         var memoryStream = new MemoryStream(); | ||||
|         await memoryStream.SaveAsAsync(sheets).ConfigureAwait(false); | ||||
|         memoryStream.Seek(0, SeekOrigin.Begin); | ||||
| @@ -365,39 +365,62 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService | ||||
|  | ||||
|     #region 导入 | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("ImportChannel", isRecordPar: false, localizerType: typeof(Channel))] | ||||
|     public Task<HashSet<long>> ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input) | ||||
|     { | ||||
|         ChannelServiceHelpers.GetImportChannelData(input, out var upData, out var insertData); | ||||
|         return ImportAsync(upData, insertData); | ||||
|         List<Channel>? channels = new List<Channel>(); | ||||
|         foreach (var item in input) | ||||
|         { | ||||
|             if (item.Key == GatewayExportString.ChannelName) | ||||
|             { | ||||
|                 var channelImports = ((ImportPreviewListOutput<Channel>)item.Value).Data; | ||||
|                 channels = channelImports; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         var upData = channels.Where(a => a.IsUp).ToList(); | ||||
|         var insertData = channels.Where(a => !a.IsUp).ToList(); | ||||
|         return ImportChannelAsync(upData, insertData); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     public async Task<HashSet<long>> ImportAsync(List<Channel> upData, List<Channel> insertData) | ||||
|     public async Task<HashSet<long>> ImportChannelAsync(List<Channel> upData, List<Channel> insertData) | ||||
|     { | ||||
|         ManageHelper.CheckChannelCount(insertData.Count); | ||||
|  | ||||
|         using var db = GetDB(); | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory > 2 * 1024 * 1024) | ||||
|         { | ||||
|             await db.BulkCopyAsync(insertData, 200000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 200000).ConfigureAwait(false); | ||||
|         } | ||||
|         else | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         { | ||||
|             await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             await db.BulkCopyAsync(insertData, 200000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 200000).ConfigureAwait(false); | ||||
|  | ||||
|         } | ||||
|         DeleteChannelFromCache(); | ||||
|         return upData.Select(a => a.Id).Concat(insertData.Select(a => a.Id)).ToHashSet(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile) | ||||
|     { | ||||
|         var path = await browserFile.StorageLocal().ConfigureAwait(false); | ||||
|         return await PreviewAsync(path).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string path) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||
| @@ -426,7 +449,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService | ||||
|     { | ||||
|         #region sheet | ||||
|  | ||||
|         if (sheetName == ExportString.ChannelName) | ||||
|         if (sheetName == GatewayExportString.ChannelName) | ||||
|         { | ||||
|             int row = 1; | ||||
|             ImportPreviewListOutput<Channel> importPreviewOutput = new(); | ||||
|   | ||||
| @@ -16,71 +16,11 @@ using ThingsGateway.Common.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public static class ChannelServiceHelpers | ||||
| public static partial class ChannelServiceHelpers | ||||
| { | ||||
|  | ||||
|     public static void GetImportChannelData(Dictionary<string, ImportPreviewOutputBase> input, out List<Channel> upData, out List<Channel> insertData) | ||||
|     { | ||||
|         List<Channel>? channels = new List<Channel>(); | ||||
|         foreach (var item in input) | ||||
|         { | ||||
|             if (item.Key == ExportString.ChannelName) | ||||
|             { | ||||
|                 var channelImports = ((ImportPreviewListOutput<Channel>)item.Value).Data; | ||||
|                 channels = channelImports; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         upData = channels.Where(a => a.IsUp).ToList(); | ||||
|         insertData = channels.Where(a => !a.IsUp).ToList(); | ||||
|     } | ||||
|  | ||||
|     public static USheetDatas ExportChannel(IEnumerable<Channel> channels) | ||||
|     { | ||||
|         var rows = ExportRows(channels); // IEnumerable 延迟执行 | ||||
|         var sheets = WrapAsSheet(ExportString.ChannelName, rows); | ||||
|         return USheetDataHelpers.GetUSheetDatas(sheets); | ||||
|     } | ||||
|  | ||||
|     internal static IEnumerable<Dictionary<string, object>> ExportRows(IEnumerable<Channel>? data) | ||||
|     { | ||||
|         if (data == null) | ||||
|             yield break; | ||||
|  | ||||
|         #region 列名称 | ||||
|  | ||||
|         var type = typeof(Channel); | ||||
|         var propertyInfos = type.GetRuntimeProperties().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>(false) == null) | ||||
|              .OrderBy( | ||||
|             a => | ||||
|             { | ||||
|                 var order = a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? int.MaxValue; | ||||
|                 if (order < 0) | ||||
|                 { | ||||
|                     order = order + 10000000; | ||||
|                 } | ||||
|                 else if (order == 0) | ||||
|                 { | ||||
|                     order = 10000000; | ||||
|                 } | ||||
|                 return order; | ||||
|             } | ||||
|             ) | ||||
|             ; | ||||
|  | ||||
|         #endregion 列名称 | ||||
|  | ||||
|         foreach (var device in data) | ||||
|         { | ||||
|             Dictionary<string, object> row = new(); | ||||
|             foreach (var prop in propertyInfos) | ||||
|             { | ||||
|                 var desc = type.GetPropertyDisplayName(prop.Name); | ||||
|                 row.Add(desc ?? prop.Name, prop.GetValue(device)?.ToString()); | ||||
|             } | ||||
|             yield return row; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal static async IAsyncEnumerable<Dictionary<string, object>> ExportRows(IAsyncEnumerable<Channel>? data) | ||||
|     { | ||||
| @@ -125,13 +65,6 @@ public static class ChannelServiceHelpers | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal static Dictionary<string, object> WrapAsSheet(string sheetName, IEnumerable<IDictionary<string, object>> rows) | ||||
|     { | ||||
|         return new Dictionary<string, object> | ||||
|         { | ||||
|             [sheetName] = rows | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     internal static Dictionary<string, object> WrapAsSheet(string sheetName, IAsyncEnumerable<IDictionary<string, object>> rows) | ||||
|     { | ||||
|   | ||||
| @@ -0,0 +1,75 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://thingsgateway.cn/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Common.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public static partial class ChannelServiceHelpers | ||||
| { | ||||
|     public static USheetDatas ExportChannel(IEnumerable<Channel> channels) | ||||
|     { | ||||
|         var rows = ExportRows(channels); // IEnumerable 延迟执行 | ||||
|         var sheets = WrapAsSheet(GatewayExportString.ChannelName, rows); | ||||
|         return USheetDataHelpers.GetUSheetDatas(sheets); | ||||
|     } | ||||
|     internal static Dictionary<string, object> WrapAsSheet(string sheetName, IEnumerable<IDictionary<string, object>> rows) | ||||
|     { | ||||
|         return new Dictionary<string, object> | ||||
|         { | ||||
|             [sheetName] = rows | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     internal static IEnumerable<Dictionary<string, object>> ExportRows(IEnumerable<Channel>? data) | ||||
|     { | ||||
|         if (data == null) | ||||
|             yield break; | ||||
|  | ||||
|         #region 列名称 | ||||
|  | ||||
|         var type = typeof(Channel); | ||||
|         var propertyInfos = type.GetRuntimeProperties().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>(false) == null) | ||||
|              .OrderBy( | ||||
|             a => | ||||
|             { | ||||
|                 var order = a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? int.MaxValue; | ||||
|                 if (order < 0) | ||||
|                 { | ||||
|                     order = order + 10000000; | ||||
|                 } | ||||
|                 else if (order == 0) | ||||
|                 { | ||||
|                     order = 10000000; | ||||
|                 } | ||||
|                 return order; | ||||
|             } | ||||
|             ) | ||||
|             ; | ||||
|  | ||||
|         #endregion 列名称 | ||||
|  | ||||
|         foreach (var device in data) | ||||
|         { | ||||
|             Dictionary<string, object> row = new(); | ||||
|             foreach (var prop in propertyInfos) | ||||
|             { | ||||
|                 var desc = type.GetPropertyDisplayName(prop.Name); | ||||
|                 row.Add(desc ?? prop.Name, prop.GetValue(device)?.ToString()); | ||||
|             } | ||||
|             yield return row; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -11,17 +11,23 @@ | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application | ||||
| { | ||||
|     public interface IChannelPageService | ||||
|     { | ||||
|         Task<string> GetPluginNameAsync(long channelId); | ||||
|  | ||||
|         Task RestartChannelAsync(long channelId); | ||||
|  | ||||
|         Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id); | ||||
|         Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|         Task CopyChannelAsync(int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long channelId, bool AutoRestartThread); | ||||
|  | ||||
|  | ||||
|         Task<QueryData<ChannelRuntime>> OnChannelQueryAsync(QueryPageOptions options); | ||||
|         Task<List<Channel>> GetChannelListAsync(QueryPageOptions options, int max = 0); | ||||
|         Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IBrowserFile file, bool restart); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 保存通道 | ||||
| @@ -39,17 +45,27 @@ namespace ThingsGateway.Gateway.Application | ||||
|         /// <param name="model">新数据</param> | ||||
|         /// <param name="restart">重启</param> | ||||
|         /// <returns></returns> | ||||
|         Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart); | ||||
|  | ||||
|  | ||||
|  | ||||
|         Task<bool> BatchEditChannelAsync(List<Channel> models, Channel oldModel, Channel model, bool restart); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 删除通道 | ||||
|         /// </summary> | ||||
|         Task<bool> DeleteChannelAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken); | ||||
|  | ||||
|         Task<bool> DeleteChannelAsync(List<long> ids, bool restart); | ||||
|         /// <summary> | ||||
|         /// 删除通道 | ||||
|         /// </summary> | ||||
|         Task<bool> ClearChannelAsync(bool restart); | ||||
|         Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart); | ||||
|         Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync(USheetDatas input, bool restart); | ||||
|         Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelFileAsync(string filePath, bool restart); | ||||
|  | ||||
|  | ||||
|         Task<USheetDatas> ExportChannelAsync(List<Channel> channels); | ||||
|  | ||||
|         Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|  | ||||
|         Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option); | ||||
|         Task<string> GetChannelNameAsync(long channelId); | ||||
|     } | ||||
| } | ||||
| @@ -16,7 +16,6 @@ namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public interface IChannelRuntimeService : IChannelPageService | ||||
| { | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 保存通道 | ||||
|     /// </summary> | ||||
| @@ -32,11 +31,14 @@ public interface IChannelRuntimeService : IChannelPageService | ||||
|     Task ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart); | ||||
|  | ||||
|  | ||||
|     Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter); | ||||
|     Task<Dictionary<string, object>> ExportChannelAsync(GatewayExportFilter exportFilter); | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile); | ||||
|     Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> data); | ||||
|     Task RestartChannelAsync(IEnumerable<ChannelRuntime> oldChannelRuntimes); | ||||
|     Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken); | ||||
|     Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken); | ||||
|     Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken); | ||||
|     Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart); | ||||
|     Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart); | ||||
|     Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart); | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -46,7 +46,7 @@ internal interface IChannelService | ||||
|     /// 导出通道为文件流结果 | ||||
|     /// </summary> | ||||
|     /// <returns>文件流结果</returns> | ||||
|     Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter); | ||||
|     Task<Dictionary<string, object>> ExportChannelAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导出通道为内存流 | ||||
| @@ -71,7 +71,7 @@ internal interface IChannelService | ||||
|     /// 报表查询 | ||||
|     /// </summary> | ||||
|     /// <param name="exportFilter">查询条件</param> | ||||
|     Task<QueryData<Channel>> PageAsync(ExportFilter exportFilter); | ||||
|     Task<QueryData<Channel>> PageAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 预览导入数据 | ||||
| @@ -102,6 +102,7 @@ internal interface IChannelService | ||||
|     Task UpdateLogAsync(long channelId, TouchSocket.Core.LogLevel logLevel); | ||||
|     Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables); | ||||
|     Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables); | ||||
|     Task<HashSet<long>> ImportAsync(List<Channel> upData, List<Channel> insertData); | ||||
|     Task<HashSet<long>> ImportChannelAsync(List<Channel> upData, List<Channel> insertData); | ||||
|  | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string path); | ||||
| } | ||||
|   | ||||
| @@ -13,9 +13,11 @@ using BootstrapBlazor.Components; | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Extension.Generic; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Collections; | ||||
| using ThingsGateway.NewLife.DictionaryExtensions; | ||||
| using ThingsGateway.NewLife.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| @@ -27,13 +29,166 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|         _logger = logger; | ||||
|     } | ||||
|  | ||||
|     public Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync() | ||||
|     { | ||||
|  | ||||
|         return Task.FromResult(GlobalData.ReadOnlyIdDevices.ToDictionary(a => a.Key, a => Tuple.Create(a.Value.Name, a.Value.PluginName))); | ||||
|     } | ||||
|  | ||||
|     public async Task<List<SelectedItem>> GetDeviceItemsAsync(bool isCollect) | ||||
|     { | ||||
|         var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false); | ||||
|         return devices.Where(a => a.IsCollect == isCollect).BuildDeviceSelectList().ToList(); | ||||
|     } | ||||
|     public Task<string> GetDevicePluginNameAsync(long id) | ||||
|     { | ||||
|         return Task.FromResult(GlobalData.ReadOnlyIdDevices.TryGetValue(id, out var deviceRuntime) ? deviceRuntime.PluginName : string.Empty); | ||||
|     } | ||||
|     public Task<string> GetDeviceNameAsync(long redundantDeviceId) | ||||
|     { | ||||
|         return Task.FromResult(GlobalData.ReadOnlyIdDevices.TryGetValue(redundantDeviceId, out var deviceRuntime) ? deviceRuntime.Name : string.Empty); | ||||
|     } | ||||
|  | ||||
|     public Task<bool> IsRedundantDeviceAsync(long id) | ||||
|     { | ||||
|         return Task.FromResult(GlobalData.IsRedundant(id)); | ||||
|     } | ||||
|  | ||||
|     public Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync(QueryPageOptions options) | ||||
|     { | ||||
|         var data = GlobalData.IdDevices.Select(a => a.Value) | ||||
|                 .WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText)) | ||||
|                 .GetQueryData(options); | ||||
|         return Task.FromResult(data); | ||||
|     } | ||||
|     public Task<List<Device>> GetDeviceListAsync(QueryPageOptions options, int max = 0) | ||||
|     { | ||||
|         var models = GlobalData.IdDevices.Select(a => a.Value) | ||||
|         .WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText)) | ||||
|         .GetData(options, out var total).Cast<Device>().ToList(); | ||||
|  | ||||
|         if (max > 0 && models.Count > max) | ||||
|         { | ||||
|             throw new("online Excel max data count 2000"); | ||||
|         } | ||||
|         return Task.FromResult(models); | ||||
|     } | ||||
|  | ||||
|     public Task<bool> ClearDeviceAsync(bool restart) | ||||
|     { | ||||
|         return DeleteDeviceAsync(GlobalData.IdChannels.Keys.ToList(), restart); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     public async Task DeviceRedundantThreadAsync(long id) | ||||
|     { | ||||
|         if (GlobalData.IdDevices.TryGetValue(id, out var deviceRuntime) && GlobalData.TryGetDeviceThreadManage(deviceRuntime, out var deviceThreadManage)) | ||||
|         { | ||||
|             await deviceThreadManage.DeviceRedundantThreadAsync(id, default).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|     public async Task RestartDeviceAsync(long id, bool deleteCache) | ||||
|     { | ||||
|         if (GlobalData.IdDevices.TryGetValue(id, out var deviceRuntime) && GlobalData.TryGetDeviceThreadManage(deviceRuntime, out var deviceThreadManage)) | ||||
|         { | ||||
|             await deviceThreadManage.RestartDeviceAsync(deviceRuntime, deleteCache).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Task PauseThreadAsync(long id) | ||||
|     { | ||||
|         if (GlobalData.IdDevices.TryGetValue(id, out var deviceRuntime)) | ||||
|         { | ||||
|             deviceRuntime.Driver?.PauseThread(!deviceRuntime.Pause); | ||||
|         } | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     public Task<USheetDatas> ExportDeviceAsync(List<Device> devices) | ||||
|     { | ||||
|         return Task.FromResult(DeviceServiceHelpers.ExportDevice(devices)); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private WaitLock WaitLock { get; set; } = new WaitLock(nameof(DeviceRuntimeService)); | ||||
|  | ||||
|     public async Task<bool> CopyAsync(Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken) | ||||
|     public async Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync(VirtualizeQueryOption option, long deviceId, long channelId) | ||||
|     { | ||||
|  | ||||
|         var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false); | ||||
|         var pluginName = GlobalData.ReadOnlyIdChannels.TryGetValue(channelId, out var channel) ? channel.PluginName : string.Empty; | ||||
|         var ret = devices.WhereIf(!option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(option.SearchText)) | ||||
|             .Where(a => a.PluginName == pluginName && a.Id != deviceId).GetQueryData(option, GatewayResourceUtil.BuildDeviceSelectList | ||||
|             ); | ||||
|  | ||||
|  | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     public Task<TouchSocket.Core.LogLevel> DeviceLogLevelAsync(long id) | ||||
|     { | ||||
|         GlobalData.IdDevices.TryGetValue(id, out var DeviceRuntime); | ||||
|         var data = DeviceRuntime?.Driver?.LogMessage?.LogLevel ?? TouchSocket.Core.LogLevel.Trace; | ||||
|         return Task.FromResult(data); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public async Task SetDeviceLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel) | ||||
|     { | ||||
|         if (GlobalData.IdDevices.TryGetValue(id, out var DeviceRuntime)) | ||||
|         { | ||||
|             if (DeviceRuntime.Driver != null) | ||||
|             { | ||||
|                 await DeviceRuntime.Driver.SetLogAsync(logLevel).ConfigureAwait(false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     public async Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread) | ||||
|     { | ||||
|         if (!GlobalData.IdDevices.TryGetValue(deviceId, out var deviceRuntime)) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Device Model = deviceRuntime.AdaptDevice(); | ||||
|         Model.Id = 0; | ||||
|         var Variables = deviceRuntime.ReadOnlyVariableRuntimes.Select(a => a.Value).AdaptListVariable(); | ||||
|  | ||||
|  | ||||
|         Dictionary<Device, List<Variable>> devices = new(); | ||||
|         for (int i = 0; i < CopyCount; i++) | ||||
|         { | ||||
|             Device device = Model.AdaptDevice(); | ||||
|             device.Id = CommonUtils.GetSingleId(); | ||||
|             device.Name = $"{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + i}"; | ||||
|             List<Variable> variables = new(); | ||||
|  | ||||
|             foreach (var item in Variables) | ||||
|             { | ||||
|                 Variable v = item.AdaptVariable(); | ||||
|                 v.Id = CommonUtils.GetSingleId(); | ||||
|                 v.DeviceId = device.Id; | ||||
|                 variables.Add(v); | ||||
|             } | ||||
|             devices.Add(device, variables); | ||||
|         } | ||||
|  | ||||
|         await GlobalData.DeviceRuntimeService.CopyAsync(devices, AutoRestartThread).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     public async Task<bool> CopyAsync(Dictionary<Device, List<Variable>> devices, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var result = await GlobalData.DeviceService.CopyAsync(devices).ConfigureAwait(false); | ||||
|  | ||||
| @@ -46,7 +201,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|             if (restart) | ||||
|             { | ||||
|                 await RuntimeServiceHelper.RestartDeviceAsync(newDeviceRuntimes).ConfigureAwait(false); | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(_logger, cancellationToken).ConfigureAwait(false); | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
| @@ -57,7 +212,11 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<bool> BatchEditAsync(IEnumerable<Device> models, Device oldModel, Device model, bool restart) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     public async Task<bool> BatchEditDeviceAsync(List<Device> models, Device oldModel, Device model, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -92,11 +251,11 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<bool> DeleteDeviceAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken) | ||||
|     public async Task<bool> DeleteDeviceAsync(List<long> ids, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var devids = ids.ToHashSet(); | ||||
|  | ||||
| @@ -111,7 +270,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|             { | ||||
|                 await RuntimeServiceHelper.RemoveDeviceAsync(deviceRuntimes).ConfigureAwait(false); | ||||
|  | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(changedDriver, _logger, cancellationToken).ConfigureAwait(false); | ||||
|                 await RuntimeServiceHelper.ChangedDriverAsync(changedDriver, _logger).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
| @@ -122,7 +281,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Task<Dictionary<string, object>> ExportDeviceAsync(ExportFilter exportFilter) => GlobalData.DeviceService.ExportDeviceAsync(exportFilter); | ||||
|     public Task<Dictionary<string, object>> ExportDeviceAsync(GatewayExportFilter exportFilter) => GlobalData.DeviceService.ExportDeviceAsync(exportFilter); | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile) => GlobalData.DeviceService.PreviewAsync(browserFile); | ||||
|     public Task<MemoryStream> ExportMemoryStream(List<Device> data, string channelName, string plugin) => | ||||
|           GlobalData.DeviceService.ExportMemoryStream(data, channelName, plugin); | ||||
| @@ -158,6 +317,119 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceAsync(IBrowserFile file, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var data = await GlobalData.DeviceService.PreviewAsync(file).ConfigureAwait(false); | ||||
|  | ||||
|             if (data.Any(a => a.Value.HasError)) return data; | ||||
|  | ||||
|             var deviceids = await GlobalData.DeviceService.ImportDeviceAsync(data).ConfigureAwait(false); | ||||
|  | ||||
|             var newDeviceRuntimes = await RuntimeServiceHelper.GetNewDeviceRuntimesAsync(deviceids).ConfigureAwait(false); | ||||
|  | ||||
|             if (restart) | ||||
|             { | ||||
|                 var newDeciceIds = newDeviceRuntimes.Select(a => a.Id).ToHashSet(); | ||||
|                 await RuntimeServiceHelper.RemoveDeviceAsync(newDeciceIds).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             //批量修改之后,需要重新加载通道 | ||||
|             RuntimeServiceHelper.Init(newDeviceRuntimes); | ||||
|  | ||||
|             //根据条件重启通道线程 | ||||
|             if (restart) | ||||
|             { | ||||
|                 await RuntimeServiceHelper.RestartDeviceAsync(newDeviceRuntimes).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             return data; | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync(USheetDatas input, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|  | ||||
|             var data = await DeviceServiceHelpers.ImportAsync(input).ConfigureAwait(false); | ||||
|  | ||||
|             if (data.Any(a => a.Value.HasError)) return data; | ||||
|  | ||||
|  | ||||
|             var deviceids = await GlobalData.DeviceService.ImportDeviceAsync(data).ConfigureAwait(false); | ||||
|  | ||||
|             var newDeviceRuntimes = await RuntimeServiceHelper.GetNewDeviceRuntimesAsync(deviceids).ConfigureAwait(false); | ||||
|  | ||||
|             if (restart) | ||||
|             { | ||||
|                 var newDeciceIds = newDeviceRuntimes.Select(a => a.Id).ToHashSet(); | ||||
|                 await RuntimeServiceHelper.RemoveDeviceAsync(newDeciceIds).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             //批量修改之后,需要重新加载通道 | ||||
|             RuntimeServiceHelper.Init(newDeviceRuntimes); | ||||
|  | ||||
|             //根据条件重启通道线程 | ||||
|             if (restart) | ||||
|             { | ||||
|                 await RuntimeServiceHelper.RestartDeviceAsync(newDeviceRuntimes).ConfigureAwait(false); | ||||
|             } | ||||
|             return data; | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceFileAsync(string filePath, bool restart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await WaitLock.WaitAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var data = await GlobalData.DeviceService.PreviewAsync(filePath).ConfigureAwait(false); | ||||
|  | ||||
|             if (data.Any(a => a.Value.HasError)) return data; | ||||
|  | ||||
|             var deviceids = await GlobalData.DeviceService.ImportDeviceAsync(data).ConfigureAwait(false); | ||||
|  | ||||
|             var newDeviceRuntimes = await RuntimeServiceHelper.GetNewDeviceRuntimesAsync(deviceids).ConfigureAwait(false); | ||||
|  | ||||
|             if (restart) | ||||
|             { | ||||
|                 var newDeciceIds = newDeviceRuntimes.Select(a => a.Id).ToHashSet(); | ||||
|                 await RuntimeServiceHelper.RemoveDeviceAsync(newDeciceIds).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             //批量修改之后,需要重新加载通道 | ||||
|             RuntimeServiceHelper.Init(newDeviceRuntimes); | ||||
|  | ||||
|             //根据条件重启通道线程 | ||||
|             if (restart) | ||||
|             { | ||||
|                 await RuntimeServiceHelper.RestartDeviceAsync(newDeviceRuntimes).ConfigureAwait(false); | ||||
|             } | ||||
|             return data; | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     public async Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart) | ||||
|     { | ||||
|         try | ||||
| @@ -209,4 +481,13 @@ public class DeviceRuntimeService : IDeviceRuntimeService | ||||
|             WaitLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task<string> ExportDeviceFileAsync(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var sheets = await GlobalData.DeviceService.ExportDeviceAsync(exportFilter).ConfigureAwait(false); | ||||
|         return await App.GetService<IImportExportService>().CreateFileAsync<Device>(sheets, "Device", false).ConfigureAwait(false); | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -200,14 +200,14 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|     /// 报表查询 | ||||
|     /// </summary> | ||||
|     /// <param name="exportFilter">查询条件</param> | ||||
|     public async Task<QueryData<Device>> PageAsync(ExportFilter exportFilter) | ||||
|     public async Task<QueryData<Device>> PageAsync(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false); | ||||
|  | ||||
|         return await QueryAsync(exportFilter.QueryPageOptions, whereQuery | ||||
|        , exportFilter.FilterKeyValueAction).ConfigureAwait(false); | ||||
|     } | ||||
|     private async Task<Func<ISugarQueryable<Device>, ISugarQueryable<Device>>> GetWhereQueryFunc(ExportFilter exportFilter) | ||||
|     private async Task<Func<ISugarQueryable<Device>, ISugarQueryable<Device>>> GetWhereQueryFunc(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         HashSet<long>? channel = null; | ||||
|         if (!exportFilter.PluginName.IsNullOrWhiteSpace()) | ||||
| @@ -229,7 +229,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|         return whereQuery; | ||||
|     } | ||||
|  | ||||
|     private async Task<Func<IEnumerable<Device>, IEnumerable<Device>>> GetWhereEnumerableFunc(ExportFilter exportFilter) | ||||
|     private async Task<Func<IEnumerable<Device>, IEnumerable<Device>>> GetWhereEnumerableFunc(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         HashSet<long>? channel = null; | ||||
|         if (!exportFilter.PluginName.IsNullOrWhiteSpace()) | ||||
| @@ -302,7 +302,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     [OperDesc("ExportDevice", isRecordPar: false, localizerType: typeof(Device))] | ||||
|     public async Task<Dictionary<string, object>> ExportDeviceAsync(ExportFilter exportFilter) | ||||
|     public async Task<Dictionary<string, object>> ExportDeviceAsync(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         //导出 | ||||
|         var devices = await GetAsyncEnumerableData(exportFilter).ConfigureAwait(false); | ||||
| @@ -321,12 +321,12 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|  | ||||
|         return sheets; | ||||
|     } | ||||
|     private async Task<IAsyncEnumerable<Device>> GetAsyncEnumerableData(ExportFilter exportFilter) | ||||
|     private async Task<IAsyncEnumerable<Device>> GetAsyncEnumerableData(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var whereQuery = await GetEnumerableData(exportFilter).ConfigureAwait(false); | ||||
|         return whereQuery.ToAsyncEnumerable(); | ||||
|     } | ||||
|     private async Task<ISugarQueryable<Device>> GetEnumerableData(ExportFilter exportFilter) | ||||
|     private async Task<ISugarQueryable<Device>> GetEnumerableData(GatewayExportFilter exportFilter) | ||||
|     { | ||||
|         var db = GetDB(); | ||||
|         var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false); | ||||
| @@ -362,12 +362,13 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("ImportDevice", isRecordPar: false, localizerType: typeof(Device))] | ||||
|     public async Task<HashSet<long>> ImportDeviceAsync(Dictionary<string, ImportPreviewOutputBase> input) | ||||
|     public Task<HashSet<long>> ImportDeviceAsync(Dictionary<string, ImportPreviewOutputBase> input) | ||||
|     { | ||||
|         IEnumerable<Device>? devices = new List<Device>(); | ||||
|  | ||||
|         IEnumerable<Device> devices = new List<Device>(); | ||||
|         foreach (var item in input) | ||||
|         { | ||||
|             if (item.Key == ExportString.DeviceName) | ||||
|             if (item.Key == GatewayExportString.DeviceName) | ||||
|             { | ||||
|                 var deviceImports = ((ImportPreviewOutput<Device>)item.Value).Data; | ||||
|                 devices = deviceImports.Select(a => a.Value); | ||||
| @@ -376,29 +377,44 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|         } | ||||
|         var upData = devices.Where(a => a.IsUp).ToList(); | ||||
|         var insertData = devices.Where(a => !a.IsUp).ToList(); | ||||
|         return ImportDeviceAsync(upData, insertData); | ||||
|  | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("ImportDevice", isRecordPar: false, localizerType: typeof(Device))] | ||||
|     public async Task<HashSet<long>> ImportDeviceAsync(List<Device> upData, List<Device> insertData) | ||||
|     { | ||||
|  | ||||
|         ManageHelper.CheckDeviceCount(insertData.Count); | ||||
|  | ||||
|         using var db = GetDB(); | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory > 2 * 1024 * 1024) | ||||
|         { | ||||
|             await db.BulkCopyAsync(insertData, 200000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 200000).ConfigureAwait(false); | ||||
|         } | ||||
|         else | ||||
|         if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false) | ||||
|         { | ||||
|             await db.BulkCopyAsync(insertData, 10000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false); | ||||
|         } | ||||
|         DeleteDeviceFromCache(); | ||||
|         return devices.Select(a => a.Id).ToHashSet(); | ||||
|         else | ||||
|         { | ||||
|  | ||||
|             await db.BulkCopyAsync(insertData, 200000).ConfigureAwait(false); | ||||
|             await db.BulkUpdateAsync(upData, 200000).ConfigureAwait(false); | ||||
|         } | ||||
|         DeleteDeviceFromCache(); | ||||
|         return upData.Select(a => a.Id).Concat(insertData.Select(a => a.Id)).ToHashSet(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile) | ||||
|     { | ||||
|         var path = await browserFile.StorageLocal().ConfigureAwait(false); // 上传文件并获取文件路径 | ||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||
|  | ||||
|         return await PreviewAsync(path).ConfigureAwait(false); | ||||
|  | ||||
|     } | ||||
|     public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string path) | ||||
|     { | ||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||
|         try | ||||
|         { | ||||
|             // 获取 Excel 文件中所有工作表的名称 | ||||
| @@ -434,8 +450,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|         { | ||||
|             FileUtility.Delete(path); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     } | ||||
|     public void SetDeviceData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, IReadOnlyDictionary<string, ChannelRuntime> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ref ImportPreviewOutput<Device> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows) | ||||
|     { | ||||
|         #region 采集设备sheet | ||||
| @@ -446,7 +462,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|         string PluginNotNull = Localizer["PluginNotNull"]; | ||||
|         string DeviceNotNull = Localizer["DeviceNotNull"]; | ||||
|  | ||||
|         if (sheetName == ExportString.DeviceName) | ||||
|         if (sheetName == GatewayExportString.DeviceName) | ||||
|         { | ||||
|             // 初始化行数 | ||||
|             int row = 1; | ||||
| @@ -484,8 +500,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|                     } | ||||
|  | ||||
|                     // 转换冗余设备名称 | ||||
|                     var hasRedundant = item.TryGetValue(ExportString.RedundantDeviceName, out var redundantObj); | ||||
|                     var hasChannel = item.TryGetValue(ExportString.ChannelName, out var channelObj); | ||||
|                     var hasRedundant = item.TryGetValue(GatewayExportString.RedundantDeviceName, out var redundantObj); | ||||
|                     var hasChannel = item.TryGetValue(GatewayExportString.ChannelName, out var channelObj); | ||||
|  | ||||
|                     // 设备ID、冗余设备ID都需要手动补录 | ||||
|                     if (hasRedundant && redundantObj != null) | ||||
| @@ -666,7 +682,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|                     } | ||||
|  | ||||
|                     // 获取设备名称 | ||||
|                     if (!item.TryGetValue(ExportString.DeviceName, out var deviceName)) | ||||
|                     if (!item.TryGetValue(GatewayExportString.DeviceName, out var deviceName)) | ||||
|                     { | ||||
|                         importPreviewOutput.HasError = true; | ||||
|                         importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull)); | ||||
| @@ -674,7 +690,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService | ||||
|                     } | ||||
|  | ||||
|                     // 转化插件名称 | ||||
|                     var value = item[ExportString.DeviceName]?.ToString(); | ||||
|                     var value = item[GatewayExportString.DeviceName]?.ToString(); | ||||
|  | ||||
|                     // 检查设备名称是否存在于设备导入预览数据中,如果不存在,则添加错误信息到导入预览结果并继续下一轮循环 | ||||
|                     var hasDevice = deviceImportPreview.Data.ContainsKey(value); | ||||
|   | ||||
| @@ -13,49 +13,13 @@ using BootstrapBlazor.Components; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Common.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public static class DeviceServiceHelpers | ||||
| public static partial class DeviceServiceHelpers | ||||
| { | ||||
|     public static USheetDatas ExportDevice(IEnumerable<Device> models) | ||||
|     { | ||||
|         var deviceDicts = GlobalData.IdDevices; | ||||
|         var channelDicts = GlobalData.IdChannels; | ||||
|         var pluginSheetNames = models.Select(a => a.ChannelId).Select(a => | ||||
|         { | ||||
|             channelDicts.TryGetValue(a, out var channel); | ||||
|             var pluginKey = channel?.PluginName; | ||||
|             return pluginKey; | ||||
|         }).ToHashSet(); | ||||
|         var data = ExportSheets(models, deviceDicts, channelDicts, pluginSheetNames); // IEnumerable 延迟执行 | ||||
|         return USheetDataHelpers.GetUSheetDatas(data); | ||||
|     } | ||||
|  | ||||
|     public static Dictionary<string, object> ExportSheets( | ||||
|         IEnumerable<Device>? data, | ||||
|         IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts, | ||||
|     IReadOnlyDictionary<long, ChannelRuntime> channelDicts, | ||||
| HashSet<string> pluginSheetNames, | ||||
|         string? channelName = null) | ||||
|     { | ||||
|         if (data?.Any() != true) | ||||
|             data = new List<Device>(); | ||||
|  | ||||
|         var result = new Dictionary<string, object>(); | ||||
|         result.Add(ExportString.DeviceName, GetDeviceSheets(data, deviceDicts, channelDicts, channelName)); | ||||
|         ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict = new(); | ||||
|  | ||||
|         foreach (var plugin in pluginSheetNames) | ||||
|         { | ||||
|             var filtered = FilterPluginDevices(data, plugin, channelDicts); | ||||
|             var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin); | ||||
|             var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin); | ||||
|             result.Add(filtResult.TypeName, pluginSheets); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     public static Dictionary<string, object> ExportSheets( | ||||
| IAsyncEnumerable<Device>? data1, | ||||
| @@ -69,7 +33,7 @@ string? channelName = null) | ||||
|             return new(); | ||||
|  | ||||
|         var result = new Dictionary<string, object>(); | ||||
|         result.Add(ExportString.DeviceName, GetDeviceSheets(data1, deviceDicts, channelDicts, channelName)); | ||||
|         result.Add(GatewayExportString.DeviceName, GetDeviceSheets(data1, deviceDicts, channelDicts, channelName)); | ||||
|         ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict = new(); | ||||
|  | ||||
|         foreach (var plugin in pluginSheetNames) | ||||
| @@ -99,61 +63,8 @@ string? channelName = null) | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     static IEnumerable<Device> FilterPluginDevices(IEnumerable<Device> data, string plugin, IReadOnlyDictionary<long, ChannelRuntime> channelDicts) | ||||
|     { | ||||
|         return data.Where(device => | ||||
|         { | ||||
|             if (channelDicts.TryGetValue(device.ChannelId, out var channel)) | ||||
|             { | ||||
|                 if (channel.PluginName == plugin) | ||||
|                     return true; | ||||
|                 else | ||||
|                     return false; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     static IEnumerable<Dictionary<string, object>> GetDeviceSheets( | ||||
|     IEnumerable<Device> data, | ||||
| IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts, | ||||
|     IReadOnlyDictionary<long, ChannelRuntime> channelDicts, | ||||
|     string? channelName) | ||||
|     { | ||||
|         var type = typeof(Device); | ||||
|         var propertyInfos = type.GetRuntimeProperties() | ||||
|             .Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>(false) == null) | ||||
|             .OrderBy(a => | ||||
|             { | ||||
|                 var order = a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? int.MaxValue; | ||||
|                 if (order < 0) order += 10000000; | ||||
|                 else if (order == 0) order = 10000000; | ||||
|                 return order; | ||||
|             }); | ||||
|  | ||||
|         foreach (var device in data) | ||||
|         { | ||||
|             yield return GetDeviceRows(device, propertyInfos, type, deviceDicts, channelDicts, channelName); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static IEnumerable<Dictionary<string, object>> GetPluginSheets( | ||||
|     IEnumerable<Device> data, | ||||
|     ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict, | ||||
|     string? plugin) | ||||
|     { | ||||
|         foreach (var device in data) | ||||
|         { | ||||
|             var row = GetPluginRows(device, plugin, propertysDict); | ||||
|             if (row != null) | ||||
|             { | ||||
|                 yield return row; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static async IAsyncEnumerable<Dictionary<string, object>> GetDeviceSheets( | ||||
|     IAsyncEnumerable<Device> data, | ||||
| @@ -197,80 +108,6 @@ IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static Dictionary<string, object> GetDeviceRows( | ||||
| Device device, | ||||
|  IEnumerable<PropertyInfo>? propertyInfos, | ||||
|  Type type, | ||||
| IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts, | ||||
|  IReadOnlyDictionary<long, ChannelRuntime>? channelDicts, | ||||
| string? channelName) | ||||
|     { | ||||
|         Dictionary<string, object> devExport = new(); | ||||
|         deviceDicts.TryGetValue(device.RedundantDeviceId ?? 0, out var redundantDevice); | ||||
|         channelDicts.TryGetValue(device.ChannelId, out var channel); | ||||
|  | ||||
|         devExport.Add(ExportString.ChannelName, channel?.Name ?? channelName); | ||||
|  | ||||
|         foreach (var item in propertyInfos) | ||||
|         { | ||||
|             //描述 | ||||
|             var desc = type.GetPropertyDisplayName(item.Name); | ||||
|             //数据源增加 | ||||
|             devExport.Add(desc ?? item.Name, item.GetValue(device)?.ToString()); | ||||
|         } | ||||
|  | ||||
|         //设备实体没有包含冗余设备名称,手动插入 | ||||
|         devExport.Add(ExportString.RedundantDeviceName, redundantDevice?.Name); | ||||
|         return devExport; | ||||
|     } | ||||
|  | ||||
|     static Dictionary<string, object> GetPluginRows(Device device, string? plugin, ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict) | ||||
|     { | ||||
|         Dictionary<string, object> driverInfo = new(); | ||||
|         var propDict = device.DevicePropertys; | ||||
|         if (!propertysDict.TryGetValue(plugin, out var propertys)) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var driverProperties = GlobalData.PluginService.GetDriver(plugin).DriverProperties; | ||||
|                 propertys.Item1 = driverProperties; | ||||
|                 var driverPropertyType = driverProperties.GetType(); | ||||
|                 propertys.Item2 = driverPropertyType.GetRuntimeProperties() | ||||
| .Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null) | ||||
| .ToDictionary(a => driverPropertyType.GetPropertyDisplayName(a.Name, a => a.GetCustomAttribute<DynamicPropertyAttribute>(true)?.Description), a => a); | ||||
|                 propertysDict.TryAdd(plugin, propertys); | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (propertys.Item2 != null) | ||||
|         { | ||||
|             if (propertys.Item2.Count > 0) | ||||
|             { | ||||
|                 //没有包含设备名称,手动插入 | ||||
|                 driverInfo.Add(ExportString.DeviceName, device.Name); | ||||
|             } | ||||
|             //根据插件的配置属性项生成列,从数据库中获取值或者获取属性默认值 | ||||
|             foreach (var item in propertys.Item2) | ||||
|             { | ||||
|                 if (propDict.TryGetValue(item.Value.Name, out var dependencyProperty)) | ||||
|                 { | ||||
|                     driverInfo.Add(item.Key, dependencyProperty); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     //添加对应属性数据 | ||||
|                     driverInfo.Add(item.Key, ThingsGatewayStringConverter.Default.Serialize(null, item.Value.GetValue(propertys.Item1))); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (driverInfo.Count > 0) | ||||
|                 return driverInfo; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public static async Task<Dictionary<string, ImportPreviewOutputBase>> ImportAsync(USheetDatas uSheetDatas) | ||||
|     { | ||||
|   | ||||
| @@ -0,0 +1,196 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://thingsgateway.cn/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Common.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public static partial class DeviceServiceHelpers | ||||
| { | ||||
|     public static USheetDatas ExportDevice(IEnumerable<Device> models) | ||||
|     { | ||||
|         var deviceDicts = GlobalData.IdDevices; | ||||
|         var channelDicts = GlobalData.IdChannels; | ||||
|         var pluginSheetNames = models.Select(a => a.ChannelId).Select(a => | ||||
|         { | ||||
|             channelDicts.TryGetValue(a, out var channel); | ||||
|             var pluginKey = channel?.PluginName; | ||||
|             return pluginKey; | ||||
|         }).ToHashSet(); | ||||
|         var data = ExportSheets(models, deviceDicts, channelDicts, pluginSheetNames); // IEnumerable 延迟执行 | ||||
|         return USheetDataHelpers.GetUSheetDatas(data); | ||||
|     } | ||||
|  | ||||
|     public static Dictionary<string, object> ExportSheets( | ||||
|     IEnumerable<Device>? data, | ||||
|     IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts, | ||||
| IReadOnlyDictionary<long, ChannelRuntime> channelDicts, | ||||
| HashSet<string> pluginSheetNames, | ||||
|     string? channelName = null) | ||||
|     { | ||||
|         if (data?.Any() != true) | ||||
|             data = new List<Device>(); | ||||
|  | ||||
|         var result = new Dictionary<string, object>(); | ||||
|         result.Add(GatewayExportString.DeviceName, GetDeviceSheets(data, deviceDicts, channelDicts, channelName)); | ||||
|         ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict = new(); | ||||
|  | ||||
|         foreach (var plugin in pluginSheetNames) | ||||
|         { | ||||
|             var filtered = FilterPluginDevices(data, plugin, channelDicts); | ||||
|             var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin); | ||||
|             var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin); | ||||
|             result.Add(filtResult.TypeName, pluginSheets); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     static IEnumerable<Dictionary<string, object>> GetDeviceSheets( | ||||
|     IEnumerable<Device> data, | ||||
| IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts, | ||||
|     IReadOnlyDictionary<long, ChannelRuntime> channelDicts, | ||||
|     string? channelName) | ||||
|     { | ||||
|         var type = typeof(Device); | ||||
|         var propertyInfos = type.GetRuntimeProperties() | ||||
|             .Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>(false) == null) | ||||
|             .OrderBy(a => | ||||
|             { | ||||
|                 var order = a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? int.MaxValue; | ||||
|                 if (order < 0) order += 10000000; | ||||
|                 else if (order == 0) order = 10000000; | ||||
|                 return order; | ||||
|             }); | ||||
|  | ||||
|         foreach (var device in data) | ||||
|         { | ||||
|             yield return GetDeviceRows(device, propertyInfos, type, deviceDicts, channelDicts, channelName); | ||||
|         } | ||||
|     } | ||||
|     static IEnumerable<Device> FilterPluginDevices(IEnumerable<Device> data, string plugin, IReadOnlyDictionary<long, ChannelRuntime> channelDicts) | ||||
|     { | ||||
|         return data.Where(device => | ||||
|         { | ||||
|             if (channelDicts.TryGetValue(device.ChannelId, out var channel)) | ||||
|             { | ||||
|                 if (channel.PluginName == plugin) | ||||
|                     return true; | ||||
|                 else | ||||
|                     return false; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     static Dictionary<string, object> GetDeviceRows( | ||||
| Device device, | ||||
|  IEnumerable<PropertyInfo>? propertyInfos, | ||||
|  Type type, | ||||
| IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts, | ||||
|  IReadOnlyDictionary<long, ChannelRuntime>? channelDicts, | ||||
| string? channelName) | ||||
|     { | ||||
|         Dictionary<string, object> devExport = new(); | ||||
|         deviceDicts.TryGetValue(device.RedundantDeviceId ?? 0, out var redundantDevice); | ||||
|         channelDicts.TryGetValue(device.ChannelId, out var channel); | ||||
|  | ||||
|         devExport.Add(GatewayExportString.ChannelName, channel?.Name ?? channelName); | ||||
|  | ||||
|         foreach (var item in propertyInfos) | ||||
|         { | ||||
|             //描述 | ||||
|             var desc = type.GetPropertyDisplayName(item.Name); | ||||
|             //数据源增加 | ||||
|             devExport.Add(desc ?? item.Name, item.GetValue(device)?.ToString()); | ||||
|         } | ||||
|  | ||||
|         //设备实体没有包含冗余设备名称,手动插入 | ||||
|         devExport.Add(GatewayExportString.RedundantDeviceName, redundantDevice?.Name); | ||||
|         return devExport; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     static IEnumerable<Dictionary<string, object>> GetPluginSheets( | ||||
|     IEnumerable<Device> data, | ||||
|     ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict, | ||||
|     string? plugin) | ||||
|     { | ||||
|         foreach (var device in data) | ||||
|         { | ||||
|             var row = GetPluginRows(device, plugin, propertysDict); | ||||
|             if (row != null) | ||||
|             { | ||||
|                 yield return row; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     static Dictionary<string, object> GetPluginRows(Device device, string? plugin, ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict) | ||||
|     { | ||||
|         Dictionary<string, object> driverInfo = new(); | ||||
|         var propDict = device.DevicePropertys; | ||||
|         if (!propertysDict.TryGetValue(plugin, out var propertys)) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var driverProperties = GlobalData.PluginService.GetDriver(plugin).DriverProperties; | ||||
|                 propertys.Item1 = driverProperties; | ||||
|                 var driverPropertyType = driverProperties.GetType(); | ||||
|                 propertys.Item2 = driverPropertyType.GetRuntimeProperties() | ||||
| .Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null) | ||||
| .ToDictionary(a => driverPropertyType.GetPropertyDisplayName(a.Name, a => a.GetCustomAttribute<DynamicPropertyAttribute>(true)?.Description), a => a); | ||||
|                 propertysDict.TryAdd(plugin, propertys); | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (propertys.Item2 != null) | ||||
|         { | ||||
|             if (propertys.Item2.Count > 0) | ||||
|             { | ||||
|                 //没有包含设备名称,手动插入 | ||||
|                 driverInfo.Add(GatewayExportString.DeviceName, device.Name); | ||||
|             } | ||||
|             //根据插件的配置属性项生成列,从数据库中获取值或者获取属性默认值 | ||||
|             foreach (var item in propertys.Item2) | ||||
|             { | ||||
|                 if (propDict.TryGetValue(item.Value.Name, out var dependencyProperty)) | ||||
|                 { | ||||
|                     driverInfo.Add(item.Key, dependencyProperty); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     //添加对应属性数据 | ||||
|                     driverInfo.Add(item.Key, ThingsGatewayStringConverter.Default.Serialize(null, item.Value.GetValue(propertys.Item1))); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (driverInfo.Count > 0) | ||||
|                 return driverInfo; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,50 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://thingsgateway.cn/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application | ||||
| { | ||||
|     public interface IDevicePageService | ||||
|     { | ||||
|         Task SetDeviceLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|         Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceAsync(IBrowserFile file, bool restart); | ||||
|  | ||||
|         Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread); | ||||
|         Task<LogLevel> DeviceLogLevelAsync(long id); | ||||
|         Task<bool> BatchEditDeviceAsync(List<Device> models, Device oldModel, Device model, bool restart); | ||||
|         Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart); | ||||
|         Task<bool> DeleteDeviceAsync(List<long> ids, bool restart); | ||||
|         Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync(USheetDatas input, bool restart); | ||||
|         Task<USheetDatas> ExportDeviceAsync(List<Device> devices); | ||||
|  | ||||
|         Task<string> ExportDeviceFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|  | ||||
|         Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync(VirtualizeQueryOption option, long deviceId, long channelId); | ||||
|         Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceFileAsync(string filePath, bool restart); | ||||
|         Task DeviceRedundantThreadAsync(long id); | ||||
|         Task RestartDeviceAsync(long id, bool deleteCache); | ||||
|         Task PauseThreadAsync(long id); | ||||
|         Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync(QueryPageOptions options); | ||||
|         Task<List<Device>> GetDeviceListAsync(QueryPageOptions option, int v); | ||||
|         Task<bool> ClearDeviceAsync(bool restart); | ||||
|         Task<bool> IsRedundantDeviceAsync(long id); | ||||
|         Task<string> GetDeviceNameAsync(long redundantDeviceId); | ||||
|         Task<List<SelectedItem>> GetDeviceItemsAsync(bool isCollect); | ||||
|         Task<string> GetDevicePluginNameAsync(long id); | ||||
|  | ||||
|         Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync(); | ||||
|     } | ||||
| } | ||||
| @@ -14,16 +14,15 @@ using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application | ||||
| { | ||||
|     public interface IDeviceRuntimeService | ||||
|     public interface IDeviceRuntimeService : IDevicePageService | ||||
|     { | ||||
|         Task<bool> BatchEditAsync(IEnumerable<Device> models, Device oldModel, Device model, bool restart); | ||||
|         Task<bool> CopyAsync(Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken); | ||||
|         Task<bool> DeleteDeviceAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken); | ||||
|         Task<Dictionary<string, object>> ExportDeviceAsync(ExportFilter exportFilter); | ||||
|  | ||||
|  | ||||
|         Task<bool> CopyAsync(Dictionary<Device, List<Variable>> devices, bool restart); | ||||
|         Task<Dictionary<string, object>> ExportDeviceAsync(GatewayExportFilter exportFilter); | ||||
|         Task<MemoryStream> ExportMemoryStream(List<Device> data, string channelName, string plugin); | ||||
|         Task ImportDeviceAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart); | ||||
|         Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile); | ||||
|         Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart); | ||||
|         Task<bool> BatchSaveDeviceAsync(List<Device> input, ItemChangedType type, bool restart); | ||||
|         Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile); | ||||
|     } | ||||
| } | ||||
| @@ -59,7 +59,7 @@ internal interface IDeviceService | ||||
|     /// 导出设备信息到文件流。 | ||||
|     /// </summary> | ||||
|     /// <returns>导出的文件流</returns> | ||||
|     Task<Dictionary<string, object>> ExportDeviceAsync(ExportFilter exportFilter); | ||||
|     Task<Dictionary<string, object>> ExportDeviceAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 导出设备信息到内存流。 | ||||
| @@ -88,7 +88,7 @@ internal interface IDeviceService | ||||
|     /// </summary> | ||||
|     /// <param name="exportFilter">查询条件</param> | ||||
|     /// <returns>查询结果</returns> | ||||
|     Task<QueryData<Device>> PageAsync(ExportFilter exportFilter); | ||||
|     Task<QueryData<Device>> PageAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 预览导入设备信息。 | ||||
| @@ -119,4 +119,6 @@ internal interface IDeviceService | ||||
|     /// 保存是否输出日志和日志等级 | ||||
|     /// </summary> | ||||
|     Task UpdateLogAsync(long deviceId, TouchSocket.Core.LogLevel logLevel); | ||||
|     Task<HashSet<long>> ImportDeviceAsync(List<Device> upData, List<Device> insertData); | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string path); | ||||
| } | ||||
|   | ||||
| @@ -94,11 +94,11 @@ public interface IManagementRpcServer : IRpcServer | ||||
|     Task RedundancyForcedSync(); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     public Task<TouchSocket.Core.LogLevel> RedundancyLogLevelAsync(); | ||||
|     Task<TouchSocket.Core.LogLevel> RedundancyLogLevelAsync(); | ||||
|     [DmtpRpc] | ||||
|     public Task SetRedundancyLogLevelAsync(TouchSocket.Core.LogLevel logLevel); | ||||
|     Task SetRedundancyLogLevelAsync(TouchSocket.Core.LogLevel logLevel); | ||||
|     [DmtpRpc] | ||||
|     public Task<string> RedundancyLogPathAsync(); | ||||
|     Task<string> RedundancyLogPathAsync(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 修改冗余设置 | ||||
| @@ -133,7 +133,7 @@ public interface IManagementRpcServer : IRpcServer | ||||
|     /// 分页显示插件 | ||||
|     /// </summary> | ||||
|     [DmtpRpc] | ||||
|     public Task<QueryData<PluginInfo>> PluginPageAsync(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null); | ||||
|     Task<QueryData<PluginInfo>> PluginPageAsync(QueryPageOptions options, PluginTypeEnum? pluginTypeEnum = null); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 重载插件 | ||||
| @@ -212,4 +212,159 @@ public interface IManagementRpcServer : IRpcServer | ||||
|     /// <param name="type">保存类型</param> | ||||
|     [DmtpRpc] | ||||
|     Task<bool> SaveRulesAsync(Rules input, ItemChangedType type); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> GetPluginNameAsync(long channelId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task RestartChannelAsync(long channelId); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|     [DmtpRpc] | ||||
|     Task CopyChannelAsync(int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long channelId, bool AutoRestartThread); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<ChannelRuntime>> OnChannelQueryAsync(QueryPageOptions options); | ||||
|     [DmtpRpc] | ||||
|     Task<List<Channel>> GetChannelListAsync(QueryPageOptions options, int max = 0); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> BatchEditChannelAsync(List<Channel> models, Channel oldModel, Channel model, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> DeleteChannelAsync(List<long> ids, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> ClearChannelAsync(bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync(USheetDatas input, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelFileAsync(string filePath, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<USheetDatas> ExportChannelAsync(List<Channel> channels); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> GetChannelNameAsync(long channelId); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task SetDeviceLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread); | ||||
|     [DmtpRpc] | ||||
|     Task<TouchSocket.Core.LogLevel> DeviceLogLevelAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> BatchEditDeviceAsync(List<Device> models, Device oldModel, Device model, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> DeleteDeviceAsync(List<long> ids, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync(USheetDatas input, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<USheetDatas> ExportDeviceAsync(List<Device> devices); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> ExportDeviceFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync(VirtualizeQueryOption option, long deviceId, long channelId); | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceFileAsync(string filePath, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task DeviceRedundantThreadAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task RestartDeviceAsync(long id, bool deleteCache); | ||||
|     [DmtpRpc] | ||||
|     Task PauseThreadAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync(QueryPageOptions options); | ||||
|     [DmtpRpc] | ||||
|     Task<List<Device>> GetDeviceListAsync(QueryPageOptions option, int v); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> ClearDeviceAsync(bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<bool> IsRedundantDeviceAsync(long id); | ||||
|     [DmtpRpc] | ||||
|     Task<string> GetDeviceNameAsync(long redundantDeviceId); | ||||
|  | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<List<SelectedItem>> GetDeviceItemsAsync(bool isCollect); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<string> GetDevicePluginNameAsync(long id); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> BatchEditVariableAsync(List<Variable> models, Variable oldModel, Variable model, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> DeleteVariableAsync(List<long> ids, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> ClearVariableAsync(bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> BatchSaveVariableAsync(List<Variable> input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<bool> SaveVariableAsync(Variable input, ItemChangedType type, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task CopyVariableAsync(List<Variable> Model, int CopyCount, string CopyVariableNamePrefix, int CopyVariableNameSuffixNumber, bool AutoRestartThread); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<QueryData<VariableRuntime>> OnVariableQueryAsync(QueryPageOptions options); | ||||
|     [DmtpRpc] | ||||
|     Task<string> ExportVariableFileAsync(GatewayExportFilter exportFilter); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<List<Variable>> GetVariableListAsync(QueryPageOptions option, int v); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<USheetDatas> ExportVariableAsync(List<Variable> models, string? sortName, SortOrder sortOrder); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableUSheetDatasAsync(USheetDatas data, bool restart); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<OperResult<object>> OnWriteVariableAsync(long id, string writeData); | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableFileAsync(string filePath, bool restart); | ||||
|     [DmtpRpc] | ||||
|     Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync(); | ||||
|  | ||||
| } | ||||
| @@ -30,8 +30,8 @@ internal sealed class ManagementHostedService : BackgroundService | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await Task.Yield(); | ||||
|         await RemoteClientManagementTask.StartAsync(stoppingToken).ConfigureAwait(false); | ||||
|         await RemoteServerManagementTask.StartAsync(stoppingToken).ConfigureAwait(false); | ||||
|         _ = RemoteClientManagementTask.StartAsync(stoppingToken); | ||||
|         _ = RemoteServerManagementTask.StartAsync(stoppingToken); | ||||
|     } | ||||
|  | ||||
|     public override async Task StopAsync(CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -10,6 +10,8 @@ | ||||
|  | ||||
| using BootstrapBlazor.Components; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
|  | ||||
| using ThingsGateway.Authentication; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| @@ -19,10 +21,9 @@ using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public partial class ManagementRpcServer : IRpcServer, IManagementRpcServer, IBackendLogService, IRpcLogService, IRestartService, IAuthenticationService, IChannelEnableService, IRedundancyHostedService, IRedundancyService, ITextFileReadService, IPluginPageService, IRealAlarmService | ||||
| public partial class ManagementRpcServer : IRpcServer, IManagementRpcServer, IBackendLogService, IRpcLogService, IRestartService, IAuthenticationService, IChannelEnableService, IRedundancyHostedService, IRedundancyService, ITextFileReadService, IPluginPageService, IRealAlarmService, IChannelPageService, IDevicePageService, IVariablePageService | ||||
| { | ||||
|  | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     public Task DeleteBackendLogAsync() => App.GetService<IBackendLogService>().DeleteBackendLogAsync(); | ||||
|     [DmtpRpc] | ||||
| @@ -127,4 +128,173 @@ public partial class ManagementRpcServer : IRpcServer, IManagementRpcServer, IBa | ||||
|  | ||||
|     public Task<bool> SaveRulesAsync(Rules input, ItemChangedType type) => App.GetService<IRulesService>().SaveRulesAsync(input, type); | ||||
|  | ||||
|     public Task<string> GetPluginNameAsync(long channelId) => App.GetService<IChannelPageService>().GetPluginNameAsync(channelId); | ||||
|  | ||||
|     public Task RestartChannelAsync(long channelId) => | ||||
|     App.GetService<IChannelPageService>().RestartChannelAsync(channelId); | ||||
|  | ||||
|     public Task<LogLevel> ChannelLogLevelAsync(long id) => | ||||
|         App.GetService<IChannelPageService>().ChannelLogLevelAsync(id); | ||||
|  | ||||
|     public Task SetChannelLogLevelAsync(long id, LogLevel logLevel) => | ||||
|         App.GetService<IChannelPageService>().SetChannelLogLevelAsync(id, logLevel); | ||||
|  | ||||
|     public Task CopyChannelAsync(int copyCount, string copyChannelNamePrefix, int copyChannelNameSuffixNumber, | ||||
|         string copyDeviceNamePrefix, int copyDeviceNameSuffixNumber, long channelId, bool restart) => | ||||
|         App.GetService<IChannelPageService>().CopyChannelAsync(copyCount, copyChannelNamePrefix, copyChannelNameSuffixNumber, | ||||
|             copyDeviceNamePrefix, copyDeviceNameSuffixNumber, channelId, restart); | ||||
|  | ||||
|     public Task<QueryData<ChannelRuntime>> OnChannelQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IChannelPageService>().OnChannelQueryAsync(options); | ||||
|  | ||||
|     public Task<List<Channel>> GetChannelListAsync(QueryPageOptions options, int max = 0) => | ||||
|         App.GetService<IChannelPageService>().GetChannelListAsync(options, max); | ||||
|  | ||||
|     public Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IChannelPageService>().SaveChannelAsync(input, type, restart); | ||||
|  | ||||
|     public Task<bool> BatchEditChannelAsync(List<Channel> models, Channel oldModel, Channel model, bool restart) => | ||||
|         App.GetService<IChannelPageService>().BatchEditChannelAsync(models, oldModel, model, restart); | ||||
|  | ||||
|     public Task<bool> DeleteChannelAsync(List<long> ids, bool restart) => | ||||
|         App.GetService<IChannelPageService>().DeleteChannelAsync(ids, restart); | ||||
|  | ||||
|     public Task<bool> ClearChannelAsync(bool restart) => | ||||
|         App.GetService<IChannelPageService>().ClearChannelAsync(restart); | ||||
|  | ||||
|     public Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelAsync(upData, insertData, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync(USheetDatas input, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelUSheetDatasAsync(input, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelFileAsync(string filePath, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelFileAsync(filePath, restart); | ||||
|  | ||||
|     public Task<USheetDatas> ExportChannelAsync(List<Channel> channels) => | ||||
|         App.GetService<IChannelPageService>().ExportChannelAsync(channels); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option) => | ||||
|         App.GetService<IChannelPageService>().OnChannelSelectedItemQueryAsync(option); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IBrowserFile file, bool restart) => | ||||
|         App.GetService<IChannelPageService>().ImportChannelAsync(file, restart); | ||||
|  | ||||
|     public Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter) => | ||||
|         App.GetService<IChannelPageService>().ExportChannelFileAsync(exportFilter); | ||||
|  | ||||
|     public Task<string> GetChannelNameAsync(long id) => | ||||
|         App.GetService<IChannelPageService>().GetChannelNameAsync(id); | ||||
|  | ||||
|     public Task SetDeviceLogLevelAsync(long id, LogLevel logLevel) => | ||||
|         App.GetService<IDevicePageService>().SetDeviceLogLevelAsync(id, logLevel); | ||||
|  | ||||
|     public Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread) => | ||||
|     App.GetService<IDevicePageService>().CopyDeviceAsync(CopyCount, CopyDeviceNamePrefix, CopyDeviceNameSuffixNumber, deviceId, AutoRestartThread); | ||||
|  | ||||
|     public Task<LogLevel> DeviceLogLevelAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().DeviceLogLevelAsync(id); | ||||
|  | ||||
|     public Task<bool> BatchEditDeviceAsync(List<Device> models, Device oldModel, Device model, bool restart) => | ||||
|         App.GetService<IDevicePageService>().BatchEditDeviceAsync(models, oldModel, model, restart); | ||||
|  | ||||
|     public Task<bool> SaveDeviceAsync(Device input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IDevicePageService>().SaveDeviceAsync(input, type, restart); | ||||
|  | ||||
|     public Task<bool> DeleteDeviceAsync(List<long> ids, bool restart) => | ||||
|         App.GetService<IDevicePageService>().DeleteDeviceAsync(ids, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync(USheetDatas input, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceUSheetDatasAsync(input, restart); | ||||
|  | ||||
|     public Task<USheetDatas> ExportDeviceAsync(List<Device> devices) => | ||||
|         App.GetService<IDevicePageService>().ExportDeviceAsync(devices); | ||||
|  | ||||
|     public Task<string> ExportDeviceFileAsync(GatewayExportFilter exportFilter) => | ||||
|         App.GetService<IDevicePageService>().ExportDeviceFileAsync(exportFilter); | ||||
|  | ||||
|     public Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync(VirtualizeQueryOption option, long deviceId, long channelId) => | ||||
|         App.GetService<IDevicePageService>().OnRedundantDevicesQueryAsync(option, deviceId, channelId); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceFileAsync(string filePath, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceFileAsync(filePath, restart); | ||||
|  | ||||
|     public Task DeviceRedundantThreadAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().DeviceRedundantThreadAsync(id); | ||||
|  | ||||
|     public Task RestartDeviceAsync(long id, bool deleteCache) => | ||||
|         App.GetService<IDevicePageService>().RestartDeviceAsync(id, deleteCache); | ||||
|  | ||||
|     public Task PauseThreadAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().PauseThreadAsync(id); | ||||
|  | ||||
|     public Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IDevicePageService>().OnDeviceQueryAsync(options); | ||||
|  | ||||
|     public Task<List<Device>> GetDeviceListAsync(QueryPageOptions option, int v) => | ||||
|         App.GetService<IDevicePageService>().GetDeviceListAsync(option, v); | ||||
|  | ||||
|     public Task<bool> ClearDeviceAsync(bool restart) => | ||||
|         App.GetService<IDevicePageService>().ClearDeviceAsync(restart); | ||||
|  | ||||
|     public Task<bool> IsRedundantDeviceAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().IsRedundantDeviceAsync(id); | ||||
|  | ||||
|     public Task<string> GetDeviceNameAsync(long redundantDeviceId) => | ||||
|         App.GetService<IDevicePageService>().GetDeviceNameAsync(redundantDeviceId); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceAsync(IBrowserFile file, bool restart) => | ||||
|         App.GetService<IDevicePageService>().ImportDeviceAsync(file, restart); | ||||
|  | ||||
|     public Task<List<SelectedItem>> GetDeviceItemsAsync(bool isCollect) => | ||||
|         App.GetService<IDevicePageService>().GetDeviceItemsAsync(isCollect); | ||||
|  | ||||
|     public Task<string> GetDevicePluginNameAsync(long id) => | ||||
|         App.GetService<IDevicePageService>().GetDevicePluginNameAsync(id); | ||||
|  | ||||
|     public Task<bool> BatchEditVariableAsync(List<Variable> models, Variable oldModel, Variable model, bool restart) => | ||||
|         App.GetService<IVariablePageService>().BatchEditVariableAsync(models, oldModel, model, restart); | ||||
|  | ||||
|     public Task<bool> DeleteVariableAsync(List<long> ids, bool restart) => | ||||
|     App.GetService<IVariablePageService>().DeleteVariableAsync(ids, restart); | ||||
|  | ||||
|     public Task<bool> ClearVariableAsync(bool restart) => | ||||
|         App.GetService<IVariablePageService>().ClearVariableAsync(restart); | ||||
|  | ||||
|     public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart) => | ||||
|         App.GetService<IVariablePageService>().InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart); | ||||
|  | ||||
|     public Task<bool> BatchSaveVariableAsync(List<Variable> input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IVariablePageService>().BatchSaveVariableAsync(input, type, restart); | ||||
|  | ||||
|     public Task<bool> SaveVariableAsync(Variable input, ItemChangedType type, bool restart) => | ||||
|         App.GetService<IVariablePageService>().SaveVariableAsync(input, type, restart); | ||||
|  | ||||
|     public Task CopyVariableAsync(List<Variable> model, int copyCount, string copyVariableNamePrefix, int copyVariableNameSuffixNumber, bool restart) => | ||||
|         App.GetService<IVariablePageService>().CopyVariableAsync(model, copyCount, copyVariableNamePrefix, copyVariableNameSuffixNumber, restart); | ||||
|  | ||||
|     public Task<QueryData<VariableRuntime>> OnVariableQueryAsync(QueryPageOptions options) => | ||||
|         App.GetService<IVariablePageService>().OnVariableQueryAsync(options); | ||||
|  | ||||
|     public Task<List<Variable>> GetVariableListAsync(QueryPageOptions option, int v) => | ||||
|         App.GetService<IVariablePageService>().GetVariableListAsync(option, v); | ||||
|  | ||||
|     public Task<USheetDatas> ExportVariableAsync(List<Variable> models, string? sortName, SortOrder sortOrder) => | ||||
|         App.GetService<IVariablePageService>().ExportVariableAsync(models, sortName, sortOrder); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableUSheetDatasAsync(USheetDatas data, bool restart) => | ||||
|         App.GetService<IVariablePageService>().ImportVariableUSheetDatasAsync(data, restart); | ||||
|  | ||||
|     public Task<OperResult<object>> OnWriteVariableAsync(long id, string writeData) => | ||||
|         App.GetService<IVariablePageService>().OnWriteVariableAsync(id, writeData); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableAsync(IBrowserFile a, bool restart) => | ||||
|         App.GetService<IVariablePageService>().ImportVariableAsync(a, restart); | ||||
|  | ||||
|     public Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableFileAsync(string filePath, bool restart) => | ||||
|         App.GetService<IVariablePageService>().ImportVariableFileAsync(filePath, restart); | ||||
|  | ||||
|     public Task<string> ExportVariableFileAsync(GatewayExportFilter exportFilter) => App.GetService<IVariablePageService>().ExportVariableFileAsync(exportFilter); | ||||
|  | ||||
|     public Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync() => App.GetService<IDevicePageService>().GetDeviceIdNamesAsync(); | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,68 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://thingsgateway.cn/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Microsoft.AspNetCore.Components.Forms; | ||||
| using Microsoft.AspNetCore.Http; | ||||
|  | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| public class UpdateZipFileAddInput1 | ||||
| { | ||||
|     /// <summary> | ||||
|     /// zip包 | ||||
|     /// </summary> | ||||
|     [Required] | ||||
|     public IFormFile ZipFile { get; set; } | ||||
|     /// <summary> | ||||
|     /// json | ||||
|     /// </summary> | ||||
|     [Required] | ||||
|     public IFormFile JsonFile { get; set; } | ||||
| } | ||||
|  | ||||
| public class UpdateZipFileAddInput | ||||
| { | ||||
|     /// <summary> | ||||
|     /// zip包 | ||||
|     /// </summary> | ||||
|     [Required] | ||||
|     public IBrowserFile ZipFile { get; set; } | ||||
|     /// <summary> | ||||
|     /// json | ||||
|     /// </summary> | ||||
|     [Required] | ||||
|     public IBrowserFile JsonFile { get; set; } | ||||
| } | ||||
| public class UpdateZipFileInput | ||||
| { | ||||
|     /// <summary> | ||||
|     /// APP名称 | ||||
|     /// </summary> | ||||
|     public string AppName { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 版本 | ||||
|     /// </summary> | ||||
|     public Version Version { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// .net版本 | ||||
|     /// </summary> | ||||
|     public Version DotNetVersion { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 系统版本 | ||||
|     /// </summary> | ||||
|     public string OSPlatform { get; set; } | ||||
|  | ||||
|     public Architecture Architecture { get; set; } | ||||
| } | ||||
| @@ -20,5 +20,9 @@ namespace ThingsGateway.Gateway.Application; | ||||
| public interface IUpgradeRpcServer : IRpcServer | ||||
| { | ||||
|     [DmtpRpc] | ||||
|     Task Upgrade(ICallContext callContext, List<UpdateZipFile> updateZipFiles); | ||||
|     Task UpgradeAsync(ICallContext callContext, UpdateZipFile updateZipFile); | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     Task<UpdateZipFileInput> GetUpdateZipFileInputAsync(ICallContext callContext); | ||||
|  | ||||
| } | ||||
| @@ -8,6 +8,9 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Reflection; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| using ThingsGateway.NewLife; | ||||
|  | ||||
| using TouchSocket.Dmtp; | ||||
| @@ -20,10 +23,24 @@ public partial class UpgradeRpcServer : IRpcServer, IUpgradeRpcServer | ||||
| { | ||||
|  | ||||
|     [DmtpRpc] | ||||
|     public async Task Upgrade(ICallContext callContext, List<UpdateZipFile> updateZipFiles) | ||||
|     public async Task UpgradeAsync(ICallContext callContext, UpdateZipFile updateZipFile) | ||||
|     { | ||||
|         if (updateZipFiles?.Count > 0 && callContext.Caller is IDmtpActorObject dmtpActorObject) | ||||
|             await Update(dmtpActorObject.DmtpActor, updateZipFiles.OrderByDescending(a => a.Version).FirstOrDefault()).ConfigureAwait(false); | ||||
|         if (callContext.Caller is IDmtpActorObject dmtpActorObject) | ||||
|             await Update(dmtpActorObject.DmtpActor, updateZipFile).ConfigureAwait(false); | ||||
|     } | ||||
|     [DmtpRpc] | ||||
|     public Task<UpdateZipFileInput> GetUpdateZipFileInputAsync(ICallContext callContext) | ||||
|     { | ||||
|         return Task.FromResult(new UpdateZipFileInput() | ||||
|         { | ||||
|             Version = Assembly.GetEntryAssembly().GetName().Version, | ||||
|             DotNetVersion = Environment.Version, | ||||
|             OSPlatform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : | ||||
|                            RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "Linux" : | ||||
|                            RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "OSX" : "Unknown", | ||||
|             Architecture = RuntimeInformation.ProcessArchitecture, | ||||
|             AppName = "ThingsGateway" | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public static async Task Update(IDmtpActor dmtpActor, UpdateZipFile updateZipFile, Func<Task<bool>> check = null) | ||||
| @@ -65,7 +82,6 @@ public partial class UpgradeRpcServer : IRpcServer, IUpgradeRpcServer | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     private static readonly WaitLock WaitLock = new(nameof(ManagementTask)); | ||||
|     private static readonly WaitLock UpdateWaitLock = new(nameof(ManagementTask)); | ||||
| } | ||||
|   | ||||
| @@ -63,7 +63,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|         const int highMemorySize = 100000; | ||||
|         const long memoryThreshold = 2L * 1024 * 1024; // 2GB,单位KB | ||||
|  | ||||
|         return GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory > memoryThreshold | ||||
|         return (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory > memoryThreshold && WebEnableVariable.WebEnable == true) | ||||
|             ? highMemorySize | ||||
|             : defaultSize; | ||||
|     } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user