Compare commits

...

6 Commits

Author SHA1 Message Date
Diego
8e3bd89f61 修改编译项 2025-08-18 17:30:34 +08:00
Diego
6da142d080 10.10.23 2025-08-18 17:03:40 +08:00
2248356998 qq.com
ff7d029e6f 更新依赖 2025-08-14 20:25:00 +08:00
2248356998 qq.com
21b4695683 10.10.19 2025-08-14 05:47:54 +08:00
Diego
02ad494a26 !71 适配远程客户端 2025-08-12 16:00:53 +00:00
2248356998 qq.com
280366e1b2 添加写优先选项,默认false 2025-08-09 13:07:08 +08:00
227 changed files with 7296 additions and 1396 deletions

1
.gitignore vendored
View File

@@ -364,6 +364,7 @@ FodyWeavers.xsd
/src/*Pro*/
/src/*Pro*
/src/**/*Pro*
/src/*pro*
/src/*pro*/
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json

View File

@@ -11,6 +11,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- 避免 DLL 被打包到 lib/ -->
<EnableSourceGenerator>true</EnableSourceGenerator>
<!-- 可选 -->

View File

@@ -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('/', '_')));
}
}

View File

@@ -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"
},

View File

@@ -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": "用户登录已过期,请重新登录"
},

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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;}" />

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -18,6 +18,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
</PropertyGroup>
<ItemGroup>

View File

@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Razor;
/// <inheritdoc/>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class ResourceUtil
public static class AdminResourceUtil
{
/// <summary>
/// 构造选择项ID/TITLE

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<!--<Import Project="Admin.targets" Condition=" '$(Configuration)' != 'Debug' " />-->

View File

@@ -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>

View File

@@ -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;
}
}

View 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"
}
}

View File

@@ -0,0 +1,13 @@
{
"ThingsGateway.DB.BaseDataEntity": {
"CreateOrgId": "创建机构Id"
},
"ThingsGateway.DB.BaseEntity": {
"CreateTime": "创建时间",
"CreateUser": "创建人",
"SortCode": "排序",
"UpdateTime": "更新时间",
"UpdateUser": "更新人"
}
}

View File

@@ -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" />-->

View File

@@ -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));

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>

View File

@@ -12,6 +12,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

View File

@@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -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]

View File

@@ -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)
{

View File

@@ -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; }
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}

View File

@@ -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"
},

View File

@@ -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": "验证内容"
},

View File

@@ -3,6 +3,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.0" />

View File

@@ -132,35 +132,43 @@ 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));
// 构建schema信息
columns.ForEach(d =>
var list = ReflectionInoCacheService.Instance.GetOrCreate($"{key}{dateFormat}List<Hashtable>", () =>
{
if (d.DataType == "TIMESTAMP")
var list = new List<Hashtable>();
// 构建schema信息
columns.ForEach(d =>
{
list.Add(new Hashtable()
if (d.DataType == "TIMESTAMP")
{
list.Add(new Hashtable()
{
{ "name", d.DbColumnName },
{ "type", d.DataType },
{ "pattern", dateFormat}
});
}
else
{
list.Add(new Hashtable()
}
else
{
list.Add(new Hashtable()
{
{ "name", d.DbColumnName },
{ "type", d.DataType }
});
}
});
}
});
return list;
}
);
var schema = JsonConvert.SerializeObject(list);
// 写入CSV文件

View File

@@ -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" />

View File

@@ -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>

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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 });
}
}
}

View File

@@ -4,6 +4,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0;</TargetFrameworks>
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
</PropertyGroup>
<ItemGroup>

View File

@@ -5,6 +5,7 @@
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoPackageAnalysis>true</NoPackageAnalysis>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
<Version>$(SourceGeneratorVersion)</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -6,6 +6,7 @@
<PropertyGroup>
<Description>工业设备通讯协议-基础类库</Description>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

View File

@@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
</PropertyGroup>

View File

@@ -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; // 当前被阻塞的读线程数
@@ -54,10 +56,13 @@ public class AsyncReadWriteLock
if (Interlocked.Increment(ref _writerCount) == 1)
{
var cancellationTokenSource = _cancellationTokenSource;
_cancellationTokenSource = new();
await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取
cancellationTokenSource.SafeDispose();
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
{

View File

@@ -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; }

View File

@@ -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; }

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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)
{
// 获取动态对象集合中指定键的属性信息

View File

@@ -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);
}

View File

@@ -17,7 +17,6 @@ using TouchSocket.Core;
using TouchSocket.Sockets;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 通道表

View File

@@ -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)

View File

@@ -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;

View File

@@ -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",

View File

@@ -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": "设备",

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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)
{

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
else
{
await db.BulkCopyAsync(insertData, 200000).ConfigureAwait(false);
await db.BulkUpdateAsync(upData, 200000).ConfigureAwait(false);
}
DeleteDeviceFromCache();
return devices.Select(a => a.Id).ToHashSet();
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);

View File

@@ -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)
{

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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)

View File

@@ -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();
}

View File

@@ -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; }
}

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -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