Compare commits

...

14 Commits

Author SHA1 Message Date
2248356998 qq.com
280366e1b2 添加写优先选项,默认false 2025-08-09 13:07:08 +08:00
2248356998 qq.com
6660ce3e34 适配远程管理客户端 2025-08-08 18:01:24 +08:00
2248356998 qq.com
7499162c1a 适配远程管理客户端 2025-08-08 02:51:42 +08:00
2248356998 qq.com
40208a5cd6 适配远程网关管理客户端 2025-08-08 02:16:05 +08:00
2248356998 qq.com
fa347f4f68 修改报警事件时间字段,增加变量表报警类 2025-08-07 19:19:36 +08:00
Diego
d7df6fc605 10.10.12 2025-08-07 11:24:57 +08:00
2248356998 qq.com
eb4bb2fd48 10.10.11 2025-08-07 10:19:28 +08:00
2248356998 qq.com
faa9858974 10.10.11 2025-08-07 10:18:22 +08:00
2248356998 qq.com
1b3d2dda49 QuestDbRestAPI 2025-08-07 10:11:37 +08:00
Diego
a8a9453611 !70 fix: questdb restapi多实例 2025-08-07 01:58:32 +00:00
2248356998 qq.com
e84f42ce14 10.10.10 2025-08-06 21:42:46 +08:00
2248356998 qq.com
6f814cf6b8 更新questdb restapi启用字段 2025-08-06 21:42:23 +08:00
2248356998 qq.com
e36432e4e9 10.10.9 2025-08-06 19:33:30 +08:00
Diego
ebd71e807b !69 更新依赖 2025-08-06 11:25:31 +00:00
329 changed files with 4965 additions and 6782 deletions

View File

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

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

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

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

@@ -39,7 +39,7 @@
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
<script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src="_framework/blazor.web.js"></script>
<!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -45,7 +45,7 @@
</app>
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/culture.js")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
<script src="_framework/blazor.server.js"></script>
<!-- PWA Service Worker -->

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)
@@ -123,7 +125,36 @@ public static class QueryPageOptionsExtensions
};
var items = datas.GetData(option, out var totalCount, where);
ret.TotalCount = totalCount;
if (totalCount > 0)
{
if (!items.Any() && option.PageIndex != 1)
{
option.PageIndex = 1;
items = datas.GetData(option, out totalCount, where);
}
}
ret.Items = items.ToList();
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

@@ -306,8 +306,19 @@ public class TimerScheduler : ILogFeature
return;
}
var func = timer.Method.As<Func<Object?, Task>>(target);
await func!(timer.State).ConfigureAwait(false);
#if NET6_0_OR_GREATER
if (timer.IsValueTask)
{
var func = timer.Method.As<Func<Object?, ValueTask>>(target);
await func!(timer.State).ConfigureAwait(false);
}
else
#endif
{
var func = timer.Method.As<Func<Object?, Task>>(target);
await func!(timer.State).ConfigureAwait(false);
}
}
catch (ThreadAbortException) { throw; }
catch (ThreadInterruptedException) { throw; }

View File

@@ -87,6 +87,8 @@ public class TimerX : ITimer, ITimerx, IDisposable
private DateTime _AbsolutelyNext;
private readonly Cron[]? _crons;
internal bool IsValueTask { get; }
#endregion
// #region 静态
@@ -158,6 +160,29 @@ public class TimerX : ITimer, ITimerx, IDisposable
Init(dueTime);
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个不可重入的定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="dueTime">多久之后开始。毫秒</param>
/// <param name="period">间隔周期。毫秒</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, Int32 dueTime, Int32 period, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (dueTime < 0) throw new ArgumentOutOfRangeException(nameof(dueTime));
IsAsyncTask = true;
Async = true;
Period = period;
Init(dueTime);
}
#endif
/// <summary>实例化一个绝对定时器指定时刻执行跟当前时间和SetNext无关</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
@@ -210,6 +235,37 @@ public class TimerX : ITimer, ITimerx, IDisposable
Init(ms);
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个绝对定时器指定时刻执行跟当前时间和SetNext无关</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="startTime">绝对开始时间</param>
/// <param name="period">间隔周期。毫秒</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, DateTime startTime, Int32 period, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (startTime <= DateTime.MinValue) throw new ArgumentOutOfRangeException(nameof(startTime));
if (period <= 0) throw new ArgumentOutOfRangeException(nameof(period));
IsAsyncTask = true;
Async = true;
Period = period;
Absolutely = true;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = startTime;
while (next < now) next = next.AddMilliseconds(period);
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Init(ms);
}
#endif
/// <summary>实例化一个Cron定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
@@ -274,6 +330,42 @@ public class TimerX : ITimer, ITimerx, IDisposable
//Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now));
}
#if NET6_0_OR_GREATER
/// <summary>实例化一个Cron定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="cronExpression">Cron表达式。支持多个表达式分号分隔</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, ValueTask> callback, Object? state, String cronExpression, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
IsValueTask = true;
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (cronExpression.IsNullOrEmpty()) throw new ArgumentNullException(nameof(cronExpression));
var list = new List<Cron>();
foreach (var item in cronExpression.Split(";"))
{
var cron = new Cron();
if (!cron.Parse(item)) throw new ArgumentException($"Invalid Cron expression[{item}]", nameof(cronExpression));
list.Add(cron);
}
_crons = list.ToArray();
IsAsyncTask = true;
Async = true;
Absolutely = true;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = _crons.Min(e => e.GetNext(now));
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Init(ms);
//Init(_AbsolutelyNext = _cron.GetNext(DateTime.Now));
}
#endif
public bool Disposed { get; private set; }
/// <summary>销毁定时器</summary>
public void Dispose()

View File

@@ -8,8 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Common.Extension;
using ThingsGateway.NewLife;
using ThingsGateway.Razor.Extension;
namespace ThingsGateway.Razor;

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 IsDisabled=@_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<ImportExcel>? 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

@@ -10,7 +10,7 @@
using Microsoft.JSInterop;
namespace ThingsGateway.Common.Extension;
namespace ThingsGateway.Razor.Extension;
/// <summary>
/// JSRuntime扩展方法
@@ -49,4 +49,28 @@ public static class JSRuntimeExtensions
{
}
}
public static async ValueTask<T> GetLocalStorage<T>(this IJSRuntime jsRuntime, string name)
{
try
{
return await jsRuntime.InvokeAsync<T>("getLocalStorage", name).ConfigureAwait(false);
}
catch
{
return default;
}
}
public static async ValueTask SetLocalStorage<T>(this IJSRuntime jsRuntime, string name, T data)
{
try
{
await jsRuntime.InvokeVoidAsync("setLocalStorage", name, data).ConfigureAwait(false);
}
catch
{
}
}
}

View File

@@ -60,14 +60,14 @@
},
"ThingsGateway.Razor.ImportExcel": {
"First": "Step 1",
"Import": "Import",
"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}, expecting to import {1} records",
"UploadCount": " Table {0}, import {1} records",
"Validate": "Validate",
"ValidateText": "Validation Content"
},

View File

@@ -60,14 +60,14 @@
},
"ThingsGateway.Razor.ImportExcel": {
"First": "第一步",
"Import": "导入",
"Import": "若验证无错误,将直接导入数据库",
"Next": "下一步",
"Reset": "重置",
"Second": "第二步",
"Third": "第三",
"Tip": "数据量较大时(大于20万)所需导入时间可能超过1分钟请耐心等待",
"Upload": "上传文件",
"UploadCount": " 表 {0}预计导入 {1} 条数据",
"UploadCount": " 表 {0},导入 {1} 条数据",
"Validate": "验证",
"ValidateText": "验证内容"
},

View File

@@ -1,9 +0,0 @@
// 设置 culture
function setCultureLocalStorage(culture) {
localStorage.setItem("culture", culture);
}
// 获取 culture
function getCultureLocalStorage() {
return localStorage.getItem("culture");
}

View File

@@ -0,0 +1,18 @@
// 设置 culture
function setCultureLocalStorage(culture) {
localStorage.setItem("culture", culture);
}
// 获取 culture
function getCultureLocalStorage() {
return localStorage.getItem("culture");
}
function getLocalStorage(name) {
return JSON.parse(localStorage.getItem(name)) ?? 0;
}
function setLocalStorage(name, data) {
if (localStorage) {
localStorage.setItem(name, JSON.stringify(data));
}
}

View File

@@ -27,16 +27,17 @@
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertDatas">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public int BulkCopy<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public int BulkCopy<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
int result = 0;
// 使用分页方式处理大数据量插入
db.Utilities.PageEach(insertDatas, pageSize, pageItems =>
{
// 同步调用批量插入API并累加结果
result += questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).GetAwaiter().GetResult();
result += questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).GetAwaiter().GetResult();
});
return result;
}
@@ -46,16 +47,17 @@
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertDatas">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public async Task<int> BulkCopyAsync<T>(IEnumerable<T> insertDatas, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
int result = 0;
// 异步分页处理大数据量插入
await db.Utilities.PageEachAsync(insertDatas, pageSize, async pageItems =>
{
// 异步调用批量插入API并累加结果
result += await questDbRestAPI.BulkCopyAsync(pageItems, dateFormat).ConfigureAwait(false);
result += await questDbRestAPI.BulkCopyAsync(pageItems, tableName, dateFormat).ConfigureAwait(false);
}).ConfigureAwait(false);
return result;
}

View File

@@ -7,16 +7,12 @@ namespace ThingsGateway.SqlSugar
/// <summary>
/// 绑定RestAPI需要的信息
/// </summary>
public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string httpPort, ref string username, ref string password)
public static void SetRestApiInfo(DbConnectionStringBuilder builder, ref string host, ref string username, ref string password)
{
if (builder.TryGetValue("Host", out object hostValue))
{
host = Convert.ToString(hostValue);
}
if (builder.TryGetValue("HttpPort", out object httpPortValue))
{
httpPort = Convert.ToString(httpPortValue);
}
if (builder.TryGetValue("Username", out object usernameValue))
{
username = Convert.ToString(usernameValue);

View File

@@ -28,16 +28,16 @@ namespace ThingsGateway.SqlSugar
/// 初始化 QuestDbRestAPI 实例
/// </summary>
/// <param name="db">SqlSugar 数据库客户端</param>
public QuestDbRestAPI(ISqlSugarClient db)
/// <param name="httpPort">restApi端口</param>
public QuestDbRestAPI(ISqlSugarClient db, int httpPort = 9000)
{
var builder = new DbConnectionStringBuilder();
builder.ConnectionString = db.CurrentConnectionConfig.ConnectionString;
this.db = db;
string httpPort = String.Empty;
string host = String.Empty;
string username = String.Empty;
string password = String.Empty;
QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref httpPort, ref username, ref password);
QuestDbRestAPHelper.SetRestApiInfo(builder, ref host, ref username, ref password);
BindHost(host, httpPort, username, password);
}
@@ -51,9 +51,14 @@ namespace ThingsGateway.SqlSugar
// HTTP GET 请求执行SQL
var result = string.Empty;
var url = $"{this.url}/exec?query={HttpUtility.UrlEncode(sql)}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrWhiteSpace(authorization))
client.DefaultRequestHeaders.Add("Authorization", authorization);
var httpResponseMessage = await client.GetAsync(url).ConfigureAwait(false);
{
request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization);
}
using var httpResponseMessage = await client.SendAsync(request).ConfigureAwait(false);
result = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
return result;
}
@@ -68,34 +73,34 @@ namespace ThingsGateway.SqlSugar
return ExecuteCommandAsync(sql).GetAwaiter().GetResult();
}
/// <summary>
/// 异步批量插入单条数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertData">要插入的数据</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>影响的行数</returns>
public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
if (db.CurrentConnectionConfig.MoreSettings == null)
db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
var sql = db.InsertableT(insertData).ToSqlString();
var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
}
///// <summary>
///// 异步批量插入单条数据
///// </summary>
///// <typeparam name="T">数据类型</typeparam>
///// <param name="insertData">要插入的数据</param>
///// <param name="dateFormat">日期格式字符串</param>
///// <returns>影响的行数</returns>
//public async Task<int> BulkCopyAsync<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
//{
// if (db.CurrentConnectionConfig.MoreSettings == null)
// db.CurrentConnectionConfig.MoreSettings = new ConnMoreSettings();
// db.CurrentConnectionConfig.MoreSettings.DisableNvarchar = true;
// var sql = db.InsertableT(insertData).ToSqlString();
// var result = await ExecuteCommandAsync(sql).ConfigureAwait(false);
// return result.Contains("OK", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
//}
/// <summary>
/// 同步批量插入单条数据
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertData">要插入的数据</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>影响的行数</returns>
public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
}
///// <summary>
///// 同步批量插入单条数据
///// </summary>
///// <typeparam name="T">数据类型</typeparam>
///// <param name="insertData">要插入的数据</param>
///// <param name="dateFormat">日期格式字符串</param>
///// <returns>影响的行数</returns>
//public int BulkCopy<T>(T insertData, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
//{
// return BulkCopyAsync(insertData, dateFormat).GetAwaiter().GetResult();
//}
/// <summary>
/// 创建分页批量插入器
@@ -115,9 +120,10 @@ namespace ThingsGateway.SqlSugar
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertList">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public async Task<int> BulkCopyAsync<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public async Task<int> BulkCopyAsync<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
var result = 0;
var fileName = $"{Guid.NewGuid()}.csv";
@@ -127,12 +133,12 @@ namespace ThingsGateway.SqlSugar
// 准备多部分表单数据
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
var list = new List<Hashtable>();
var name = db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
tableName ??= db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
// 获取或创建列信息缓存
var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode();
var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () =>
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(name));
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName));
// 构建schema信息
columns.ForEach(d =>
@@ -170,8 +176,8 @@ namespace ThingsGateway.SqlSugar
// 准备HTTP请求内容
using var httpContent = new MultipartFormDataContent(boundary);
using var fileStream = File.OpenRead(filePath);
if (!string.IsNullOrWhiteSpace(this.authorization))
client.DefaultRequestHeaders.Add("Authorization", this.authorization);
//if (!string.IsNullOrWhiteSpace(this.authorization))
// client.DefaultRequestHeaders.Add("Authorization", this.authorization);
httpContent.Add(new StringContent(schema), "schema");
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
@@ -183,8 +189,8 @@ namespace ThingsGateway.SqlSugar
"multipart/form-data; boundary=" + boundary);
// 发送请求并处理响应
var httpResponseMessage =
await Post(client, name, httpContent).ConfigureAwait(false);
using var httpResponseMessage =
await Post(client, tableName, httpContent).ConfigureAwait(false);
var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
@@ -266,11 +272,12 @@ namespace ThingsGateway.SqlSugar
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="insertList">要插入的数据列表</param>
/// <param name="tableName">表名称</param>
/// <param name="dateFormat">日期格式字符串</param>
/// <returns>插入的记录数</returns>
public int BulkCopy<T>(List<T> insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
public int BulkCopy<T>(List<T> insertList, string tableName = null, string dateFormat = "yyyy/M/d H:mm:ss") where T : class, new()
{
return BulkCopyAsync(insertList, dateFormat).GetAwaiter().GetResult();
return BulkCopyAsync(insertList, tableName, dateFormat).GetAwaiter().GetResult();
}
/// <summary>
@@ -280,7 +287,7 @@ namespace ThingsGateway.SqlSugar
/// <param name="httpPort">HTTP端口</param>
/// <param name="username">用户名</param>
/// <param name="password">密码</param>
private void BindHost(string host, string httpPort, string username, string password)
private void BindHost(string host, int httpPort, string username, string password)
{
url = host;
if (url.EndsWith('/'))

View File

@@ -2,9 +2,9 @@
{
public static class QuestDbSqlSugarClientExtensions
{
public static QuestDbRestAPI RestApi(this ISqlSugarClient db)
public static QuestDbRestAPI RestApi(this ISqlSugarClient db, int httpPort = 9000)
{
return new QuestDbRestAPI(db);
return new QuestDbRestAPI(db, httpPort);
}
}
}

View File

@@ -8,6 +8,6 @@
V Get<V>(string key);
IEnumerable<string> GetAllKey<V>();
void Remove<V>(string key);
V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue);
V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600);
}
}

View File

@@ -31,7 +31,7 @@ namespace ThingsGateway.SqlSugar
return ReflectionInoCore<V>.GetInstance().GetAllKey();
}
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue)
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = 3600)
{
return ReflectionInoCore<V>.GetInstance().GetOrCreate(cacheKey, create);
}
@@ -43,10 +43,13 @@ namespace ThingsGateway.SqlSugar
}
public class ReflectionInoCore<V>
{
private MemoryCache InstanceCache => new MemoryCache() { Expire = 60 };
private MemoryCache InstanceCache = new MemoryCache() { Expire = 180 };
private static ReflectionInoCore<V> _instance = null;
private static readonly object _instanceLock = new object();
private ReflectionInoCore() { }
private ReflectionInoCore()
{
}
public V this[string key]
{
@@ -107,10 +110,10 @@ namespace ThingsGateway.SqlSugar
return this.InstanceCache.Keys;
}
public V GetOrCreate(string cacheKey, Func<V> create)
public V GetOrCreate(string cacheKey, Func<V> create, int expire = 3600)
{
return InstanceCache.GetOrAdd<V>(cacheKey, (a) =>
create());
create(), expire);
}
}
public static class ReflectionInoHelper

View File

@@ -447,6 +447,28 @@ namespace ThingsGateway.SqlSugar
}
public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
{
if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>();
string cacheKey = "QuestDB.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId;
cacheKey = GetCacheKey(cacheKey);
if (isCache)
{
return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
return GetColInfo(tableName);
});
}
else
{
return GetColInfo(tableName);
}
}
private List<DbColumnInfo> GetColInfo(string tableName)
{
var sql = String.Format(GetColumnInfosByTableNameSql, tableName);
List<DbColumnInfo> result = new List<DbColumnInfo>();

View File

@@ -717,8 +717,32 @@ namespace ThingsGateway.SqlSugar
/// <returns>列信息列表</returns>
public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
{
var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 ";
if (string.IsNullOrEmpty(tableName)) return new List<DbColumnInfo>();
string cacheKey = "TDengine.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower() + this.Context.CurrentConnectionConfig.ConfigId;
cacheKey = GetCacheKey(cacheKey);
if (isCache)
{
return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
return GetColInfo(tableName);
});
}
else
{
return GetColInfo(tableName);
}
}
private List<DbColumnInfo> GetColInfo(string tableName)
{
List<DbColumnInfo> result = new List<DbColumnInfo>();
var sql = $"select * from {this.SqlBuilder.GetTranslationColumnName(tableName)} where 1=2 ";
DataTable dt = null;
try
{

View File

@@ -1,9 +1,9 @@
<Project>
<PropertyGroup>
<PluginVersion>10.10.4</PluginVersion>
<ProPluginVersion>10.10.4</ProPluginVersion>
<DefaultVersion>10.10.7</DefaultVersion>
<PluginVersion>10.10.16</PluginVersion>
<ProPluginVersion>10.10.16</ProPluginVersion>
<DefaultVersion>10.10.16</DefaultVersion>
<AuthenticationVersion>10.10.1</AuthenticationVersion>
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
<NET8Version>8.0.19</NET8Version>

View File

@@ -77,7 +77,8 @@ public partial class LogConsole : IDisposable
[Inject]
private ToastService ToastService { get; set; }
[Inject]
ITextFileReadService TextFileReadService { get; set; }
public void Dispose()
{
Disposed = true;
@@ -94,7 +95,7 @@ public partial class LogConsole : IDisposable
if (LogPath != null)
{
var files = TextFileReader.GetFiles(LogPath);
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
if (!files.IsSuccess)
{
Messages = new List<LogMessage>();
@@ -105,7 +106,7 @@ public partial class LogConsole : IDisposable
await Task.Run(async () =>
{
Stopwatch sw = Stopwatch.StartNew();
var result = TextFileReader.LastLog(files.Content.FirstOrDefault());
var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault());
if (result.IsSuccess)
{
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
@@ -143,7 +144,7 @@ public partial class LogConsole : IDisposable
{
if (LogPath != null)
{
var files = TextFileReader.GetFiles(LogPath);
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
if (files.IsSuccess)
{
foreach (var item in files.Content)

View File

@@ -26,7 +26,7 @@ public class PlatformService : IPlatformService
public async Task OnLogExport(string logPath)
{
var files = TextFileReader.GetFiles(logPath);
var files = TextFileReader.GetLogFilesAsync(logPath);
if (!files.IsSuccess)
{
return;

View File

@@ -33,5 +33,6 @@ public class Startup : AppStartup
}
services.AddScoped<IPlatformService, PlatformService>();
services.AddSingleton<ITextFileReadService, TextFileReadService>();
}
}

View File

@@ -59,7 +59,7 @@ public static class TextFileReader
/// </summary>
/// <param name="directoryPath">目录路径</param>
/// <returns>包含文件信息的列表</returns>
public static OperResult<List<string>> GetFiles(string directoryPath)
public static OperResult<List<string>> GetLogFilesAsync(string directoryPath)
{
OperResult<List<string>> result = new(); // 初始化结果对象
// 检查目录是否存在
@@ -91,7 +91,7 @@ public static class TextFileReader
return result;
}
public static OperResult<List<LogData>> LastLog(string file, int lineCount = 200)
public static OperResult<List<LogData>> LastLogDataAsync(string file, int lineCount = 200)
{
if (!File.Exists(file))
return new OperResult<List<LogData>>("The file path is invalid");
@@ -104,7 +104,7 @@ public static class TextFileReader
{
var fileInfo = new FileInfo(file);
var length = fileInfo.Length;
var cacheKey = $"{nameof(TextFileReader)}_{nameof(LastLog)}_{file})";
var cacheKey = $"{nameof(TextFileReader)}_{nameof(LastLogDataAsync)}_{file})";
if (_cache.TryGetValue<LogDataCache>(cacheKey, out var cachedData))
{
if (cachedData != null && cachedData.Length == length)

View File

@@ -0,0 +1,24 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
public interface ITextFileReadService
{
/// <summary>
/// 获取指定目录下所有文件信息
/// </summary>
/// <param name="directoryPath">目录路径</param>
/// <returns>包含文件信息的列表</returns>
public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath);
public Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200);
}

View File

@@ -0,0 +1,20 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
public class TextFileReadService : ITextFileReadService
{
public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath) => Task.FromResult(TextFileReader.GetLogFilesAsync(directoryPath));
public Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200) => Task.FromResult(TextFileReader.LastLogDataAsync(file, lineCount));
}

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; // 当前被阻塞的读线程数
@@ -33,6 +35,8 @@ public class AsyncReadWriteLock
{
Interlocked.Increment(ref _readerCount);
// 第一个读者需要获取写入锁,防止写操作
await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
@@ -52,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);
@@ -63,11 +70,16 @@ public class AsyncReadWriteLock
private object lockObject = new();
private void ReleaseWriter()
{
var writerCount = Interlocked.Decrement(ref _writerCount);
// 每次释放写时,总是唤醒至少一个读
_readerLock.Set();
if (writerCount == 0)
{
var resetEvent = _readerLock;
_readerLock = new(false);
//_readerLock = new(false);
Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
resetEvent.SetAll();
}
@@ -83,18 +95,28 @@ public class AsyncReadWriteLock
if (count >= _writeReadRatio)
{
Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
_readerLock.Set();
//_readerLock.Set();
}
}
else
{
_readerLock.Set();
//_readerLock.Set();
}
}
}
}
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

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

@@ -0,0 +1,57 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
public class LinkedCancellationTokenSourceCache : IDisposable
{
private CancellationTokenSource? _cachedCts;
private CancellationToken _token1;
private CancellationToken _token2;
private readonly object _lock = new();
~LinkedCancellationTokenSourceCache()
{
Dispose();
}
/// <summary>
/// 获取一个 CancellationTokenSource它是由两个 token 链接而成的。
/// 会尝试复用之前缓存的 CTS前提是两个 token 仍然相同且未取消。
/// </summary>
public CancellationTokenSource GetLinkedTokenSource(CancellationToken token1, CancellationToken token2)
{
lock (_lock)
{
// 如果缓存的 CTS 已经取消或 Dispose或者 token 不同,重新创建
if (_cachedCts?.IsCancellationRequested != false ||
!_token1.Equals(token1) || !_token2.Equals(token2))
{
_cachedCts?.Dispose();
_cachedCts = CancellationTokenSource.CreateLinkedTokenSource(token1, token2);
_token1 = token1;
_token2 = token2;
}
return _cachedCts;
}
}
public void Dispose()
{
lock (_lock)
{
_cachedCts?.Dispose();
_cachedCts = null!;
}
}
}

View File

@@ -10,6 +10,7 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
private int _interval10MS = 10;
private string _interval;
private readonly Func<object?, CancellationToken, Task> _taskFunc;
private readonly Func<object?, CancellationToken, ValueTask> _valueTaskFunc;
private readonly Action<object?, CancellationToken> _taskAction;
private readonly CancellationToken _token;
private TimerX? _timer;
@@ -28,6 +29,17 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
_taskFunc = taskFunc;
_token = token;
}
public CronScheduledTask(string interval, Func<object?, CancellationToken, ValueTask> taskFunc, object? state, ILog log, CancellationToken token)
{
_interval = interval;
LogMessage = log;
_state = state;
_valueTaskFunc = taskFunc;
_token = token;
}
public CronScheduledTask(string interval, Action<object?, CancellationToken> taskAction, object? state, ILog log, CancellationToken token)
{
_interval = interval;
@@ -51,14 +63,14 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
if (Check()) return;
if (_taskAction != null)
_timer = new TimerX(TimerCallback, _state, _interval, nameof(IScheduledTask)) { Async = true };
else if (_taskFunc != null)
else if (_taskFunc != null || _valueTaskFunc != null)
_timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(IScheduledTask)) { Async = true };
}
private async Task TimerCallbackAsync(object? state)
private async ValueTask TimerCallbackAsync(object? state)
{
if (Check()) return;
if (_taskFunc == null)
if (_taskFunc == null && _valueTaskFunc == null)
{
Dispose();
return;
@@ -74,7 +86,10 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
try
{
await _taskFunc(state, _token).ConfigureAwait(false);
if (_taskFunc != null)
await _taskFunc(state, _token).ConfigureAwait(false);
else if (_valueTaskFunc != null)
await _valueTaskFunc(state, _token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{

View File

@@ -10,6 +10,7 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
private int _interval10MS = 10;
public int IntervalMS { get; }
private readonly Func<object?, CancellationToken, Task> _taskFunc;
private readonly Func<object?, CancellationToken, ValueTask> _valueTaskFunc;
private readonly CancellationToken _token;
private TimerX? _timer;
private object? _state;
@@ -26,6 +27,14 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
_taskFunc = taskFunc;
_token = token;
}
public ScheduledAsyncTask(int interval, Func<object?, CancellationToken, ValueTask> taskFunc, object? state, ILog log, CancellationToken token)
{
IntervalMS = interval;
LogMessage = log;
_state = state;
_valueTaskFunc = taskFunc;
_token = token;
}
private bool Check()
{
if (_token.IsCancellationRequested)
@@ -42,11 +51,17 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
_timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(IScheduledTask)) { Async = true };
}
private async Task DoAsync(object? state)
private async ValueTask DoAsync(object? state)
{
if (Check())
return;
if (_taskFunc == null && _valueTaskFunc == null)
{
Dispose();
return;
}
Interlocked.Increment(ref _pendingTriggers);
if (Interlocked.Exchange(ref _isRunning, 1) == 1)
@@ -55,9 +70,13 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
// 减少一个触发次数
Interlocked.Decrement(ref _pendingTriggers);
try
{
await _taskFunc(state, _token).ConfigureAwait(false);
if (_taskFunc != null)
await _taskFunc(state, _token).ConfigureAwait(false);
else if (_valueTaskFunc != null)
await _valueTaskFunc(state, _token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{

View File

@@ -14,6 +14,18 @@ public static class ScheduledTaskHelper
return new CronScheduledTask(interval, func, state, log, cancellationToken);
}
}
public static IScheduledTask GetTask(string interval, Func<object?, CancellationToken, ValueTask> func, object? state, TouchSocket.Core.ILog log, CancellationToken cancellationToken)
{
if (int.TryParse(interval, out int intervalV))
{
var intervalMilliseconds = intervalV < 10 ? 10 : intervalV;
return new ScheduledAsyncTask(intervalMilliseconds, func, state, log, cancellationToken);
}
else
{
return new CronScheduledTask(interval, func, state, log, cancellationToken);
}
}
public static IScheduledTask GetTask(string interval, Action<object?, CancellationToken> action, object? state, TouchSocket.Core.ILog log, CancellationToken cancellationToken)
{
if (int.TryParse(interval, out int intervalV))

View File

@@ -10,7 +10,7 @@
namespace ThingsGateway.Gateway.Application;
public static class ExportString
public static class GatewayExportString
{
/// <summary>
/// 通道名称
@@ -35,13 +35,19 @@ public static class ExportString
/// </summary>
public static string VariableName => Localizer["VariableName"];
/// <summary>
/// 变量报警表名称
/// </summary>
public static string AlarmName => Localizer["AlarmName"];
public static IStringLocalizer localizer;
public static IStringLocalizer Localizer
{
get
{
if (localizer == null)
localizer = App.CreateLocalizerByType(typeof(ExportString));
localizer = App.CreateLocalizerByType(typeof(GatewayExportString));
return localizer;
}
}

View File

@@ -1,43 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RouteAttribute = Microsoft.AspNetCore.Mvc.RouteAttribute;
namespace ThingsGateway.Management;
[ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)]
[Route("openApi/autoUpdate")]
[RolePermission]
[RequestAudit]
[ApiController]
[Authorize(AuthenticationSchemes = "Bearer")]
public class AutoUpdateController : ControllerBase
{
private IUpdateZipFileHostedService _updateZipFileService;
public AutoUpdateController(IUpdateZipFileHostedService updateZipFileService)
{
_updateZipFileService = updateZipFileService;
}
/// <summary>
/// 检查更新
/// </summary>
/// <returns></returns>
[HttpPost("update")]
public async Task Update()
{
var data = await _updateZipFileService.GetList().ConfigureAwait(false);
if (data.Count != 0)
await _updateZipFileService.Update(data.OrderByDescending(a => a.Version).FirstOrDefault()).ConfigureAwait(false);
}
}

View File

@@ -15,14 +15,10 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO.Ports;
using ThingsGateway.FriendlyException;
using TouchSocket.Core;
using TouchSocket.Rpc;
using TouchSocket.Sockets;
namespace ThingsGateway.Gateway.Application;
@@ -156,9 +152,9 @@ public class ControlController : ControllerBase, IRpcServer
[HttpPost("batchSaveChannel")]
[DisplayName("保存通道")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> BatchSaveChannelAsync([FromBody][TouchSocket.WebApi.FromBody] List<ChannelInput> channels, ItemChangedType type, bool restart = true)
public Task<bool> BatchSaveChannelAsync([FromBody][TouchSocket.WebApi.FromBody] List<Channel> channels, ItemChangedType type, bool restart = true)
{
return GlobalData.ChannelRuntimeService.BatchSaveChannelAsync(channels.AdaptListChannel(), type, restart);
return GlobalData.ChannelRuntimeService.BatchSaveChannelAsync(channels, type, restart);
}
/// <summary>
@@ -167,9 +163,9 @@ public class ControlController : ControllerBase, IRpcServer
[HttpPost("batchSaveDevice")]
[DisplayName("保存设备")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> BatchSaveDeviceAsync([FromBody][TouchSocket.WebApi.FromBody] List<DeviceInput> devices, ItemChangedType type, bool restart = true)
public Task<bool> BatchSaveDeviceAsync([FromBody][TouchSocket.WebApi.FromBody] List<Device> devices, ItemChangedType type, bool restart = true)
{
return GlobalData.DeviceRuntimeService.BatchSaveDeviceAsync(devices.AdaptListDevice(), type, restart);
return GlobalData.DeviceRuntimeService.BatchSaveDeviceAsync(devices, type, restart);
}
/// <summary>
@@ -178,9 +174,9 @@ public class ControlController : ControllerBase, IRpcServer
[HttpPost("batchSaveVariable")]
[DisplayName("保存变量")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> BatchSaveVariableAsync([FromBody][TouchSocket.WebApi.FromBody] List<VariableInput> variables, ItemChangedType type, bool restart = true)
public Task<bool> BatchSaveVariableAsync([FromBody][TouchSocket.WebApi.FromBody] List<Variable> variables, ItemChangedType type, bool restart = true)
{
return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables.AdaptListVariable(), type, restart, default);
return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables, type, restart, default);
}
/// <summary>
@@ -247,552 +243,3 @@ public class ControlController : ControllerBase, IRpcServer
}
}
}
public class ChannelInput
{
/// <summary>
/// 主键Id
/// </summary>
public virtual long Id { get; set; }
/// <summary>
/// 通道名称
/// </summary>
[Required]
public virtual string Name { get; set; }
/// <inheritdoc/>
public virtual ChannelTypeEnum ChannelType { get; set; }
/// <summary>
/// 插件名称
/// </summary>
[Required]
public virtual string PluginName { get; set; }
/// <summary>
/// 使能
/// </summary>
public virtual bool Enable { get; set; } = true;
/// <summary>
/// LogLevel
/// </summary>
public LogLevel LogLevel { get; set; } = LogLevel.Info;
/// <summary>
/// 远程地址,可由<see cref="IPHost"/> 与 <see cref="string"/> 相互转化
/// </summary>
[UriValidation]
public virtual string RemoteUrl { get; set; } = "127.0.0.1:502";
/// <summary>
/// 本地地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
/// </summary>
[UriValidation]
public virtual string BindUrl { get; set; }
/// <summary>
/// COM
/// </summary>
public virtual string PortName { get; set; } = "COM1";
/// <summary>
/// 波特率
/// </summary>
public virtual int BaudRate { get; set; } = 9600;
/// <summary>
/// 数据位
/// </summary>
public virtual int DataBits { get; set; } = 8;
/// <summary>
/// 校验位
/// </summary>
public virtual Parity Parity { get; set; }
/// <summary>
/// 停止位
/// </summary>
public virtual StopBits StopBits { get; set; } = StopBits.One;
/// <summary>
/// DtrEnable
/// </summary>
public virtual bool DtrEnable { get; set; }
/// <summary>
/// RtsEnable
/// </summary>
public virtual bool RtsEnable { get; set; }
/// <summary>
/// StreamAsync
/// </summary>
public virtual bool StreamAsync { get; set; } = false;
/// <summary>
/// 缓存超时
/// </summary>
[MinValue(100)]
public virtual int CacheTimeout { get; set; } = 500;
/// <summary>
/// 连接超时
/// </summary>
[MinValue(100)]
public virtual ushort ConnectTimeout { get; set; } = 3000;
/// <summary>
/// 最大并发数
/// </summary>
[MinValue(1)]
public virtual int MaxConcurrentCount { get; set; } = 1;
public virtual int MaxClientCount { get; set; } = 10000;
public virtual int CheckClearTime { get; set; } = 120000;
public virtual string Heartbeat { get; set; } = "Heartbeat";
#region dtu终端
public virtual int HeartbeatTime { get; set; } = 60000;
public virtual string DtuId { get; set; }
#endregion
public virtual DtuSeviceType DtuSeviceType { get; set; }
}
public class DeviceInput : IValidatableObject
{
public long Id { get; set; }
/// <summary>
/// 名称
/// </summary>
[Required]
[RegularExpression(@"^[^.]*$", ErrorMessage = "The field {0} cannot contain a dot ('.')")]
public virtual string Name { get; set; }
/// <summary>
/// 描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 通道
/// </summary>
[MinValue(1)]
[Required]
public virtual long ChannelId { get; set; }
/// <summary>
/// 默认执行间隔支持corn表达式
/// </summary>
public virtual string IntervalTime { get; set; } = "1000";
/// <summary>
/// 设备使能
/// </summary>
public virtual bool Enable { get; set; } = true;
/// <summary>
/// LogLevel
/// </summary>
public virtual TouchSocket.Core.LogLevel LogLevel { get; set; } = TouchSocket.Core.LogLevel.Info;
/// <summary>
/// 设备属性Json
/// </summary>
public Dictionary<string, string>? DevicePropertys { get; set; } = new();
#region
/// <summary>
/// 启用冗余
/// </summary>
public bool RedundantEnable { get; set; }
/// <summary>
/// 冗余设备Id,只能选择相同驱动
/// </summary>
public long? RedundantDeviceId { get; set; }
/// <summary>
/// 冗余模式
/// </summary>
public virtual RedundantSwitchTypeEnum RedundantSwitchType { get; set; }
/// <summary>
/// 冗余扫描间隔
/// </summary>
[MinValue(30000)]
public virtual int RedundantScanIntervalTime { get; set; } = 30000;
/// <summary>
/// 冗余切换判断脚本返回true则切换冗余设备
/// </summary>
public virtual string RedundantScript { get; set; }
#endregion
#region
/// <summary>
/// 自定义
/// </summary>
public string? Remark1 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark2 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark3 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark4 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark5 { get; set; }
#endregion
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (RedundantEnable && RedundantDeviceId == null)
{
yield return new ValidationResult("When enable redundancy, you must select a redundant device.", new[] { nameof(RedundantEnable), nameof(RedundantDeviceId) });
}
}
}
public class VariableInput : IValidatableObject
{
public virtual long Id { get; set; }
/// <summary>
/// 设备
/// </summary>
[Required]
public virtual long DeviceId { get; set; }
/// <summary>
/// 变量名称
/// </summary>
[Required]
public virtual string Name { get; set; }
/// <summary>
/// 描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 单位
/// </summary>
public virtual string? Unit { get; set; }
/// <summary>
/// 间隔时间
/// </summary>
public virtual string? IntervalTime { get; set; }
/// <summary>
/// 变量地址,可能带有额外的信息,比如<see cref="DataFormatEnum"/> ,以;分割
/// </summary>
public string? RegisterAddress { get; set; }
/// <summary>
/// 数组长度
/// </summary>
public int? ArrayLength { get; set; }
/// <summary>
/// 其他方法若不为空此时RegisterAddress为方法参数
/// </summary>
public string? OtherMethod { get; set; }
/// <summary>
/// 使能
/// </summary>
public virtual bool Enable { get; set; } = true;
/// <summary>
/// 读写权限
/// </summary>
public virtual ProtectTypeEnum ProtectType { get; set; } = ProtectTypeEnum.ReadWrite;
/// <summary>
/// 数据类型
/// </summary>
public virtual DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16;
/// <summary>
/// 读取表达式
/// </summary>
public virtual string? ReadExpressions { get; set; }
/// <summary>
/// 写入表达式
/// </summary>
public virtual string? WriteExpressions { get; set; }
/// <summary>
/// 是否允许远程Rpc写入
/// </summary>
public virtual bool RpcWriteEnable { get; set; } = true;
/// <summary>
/// 初始值
/// </summary>
public object? InitValue
{
get
{
return _value;
}
set
{
if (value != null)
_value = value?.ToString()?.GetJTokenFromString();
else
_value = null;
}
}
private object? _value;
/// <summary>
/// 保存初始值
/// </summary>
public virtual bool SaveValue { get; set; } = false;
/// <summary>
/// 变量额外属性Json
/// </summary>
public Dictionary<long, Dictionary<string, string>>? VariablePropertys { get; set; }
#region
/// <summary>
/// 报警延时
/// </summary>
public int AlarmDelay { get; set; }
/// <summary>
/// 布尔开报警使能
/// </summary>
public bool BoolOpenAlarmEnable { get; set; }
/// <summary>
/// 布尔开报警约束
/// </summary>
public string? BoolOpenRestrainExpressions { get; set; }
/// <summary>
/// 布尔开报警文本
/// </summary>
public string? BoolOpenAlarmText { get; set; }
/// <summary>
/// 布尔关报警使能
/// </summary>
public bool BoolCloseAlarmEnable { get; set; }
/// <summary>
/// 布尔关报警约束
/// </summary>
public string? BoolCloseRestrainExpressions { get; set; }
/// <summary>
/// 布尔关报警文本
/// </summary>
public string? BoolCloseAlarmText { get; set; }
/// <summary>
/// 高报使能
/// </summary>
public bool HAlarmEnable { get; set; }
/// <summary>
/// 高报约束
/// </summary>
public string? HRestrainExpressions { get; set; }
/// <summary>
/// 高报文本
/// </summary>
public string? HAlarmText { get; set; }
/// <summary>
/// 高限值
/// </summary>
public double? HAlarmCode { get; set; }
/// <summary>
/// 高高报使能
/// </summary>
public bool HHAlarmEnable { get; set; }
/// <summary>
/// 高高报约束
/// </summary>
public string? HHRestrainExpressions { get; set; }
/// <summary>
/// 高高报文本
/// </summary>
public string? HHAlarmText { get; set; }
/// <summary>
/// 高高限值
/// </summary>
public double? HHAlarmCode { get; set; }
/// <summary>
/// 低报使能
/// </summary>
public bool LAlarmEnable { get; set; }
/// <summary>
/// 低报约束
/// </summary>
public string? LRestrainExpressions { get; set; }
/// <summary>
/// 低报文本
/// </summary>
public string? LAlarmText { get; set; }
/// <summary>
/// 低限值
/// </summary>
public double? LAlarmCode { get; set; }
/// <summary>
/// 低低报使能
/// </summary>
public bool LLAlarmEnable { get; set; }
/// <summary>
/// 低低报约束
/// </summary>
public string? LLRestrainExpressions { get; set; }
/// <summary>
/// 低低报文本
/// </summary>
public string? LLAlarmText { get; set; }
/// <summary>
/// 低低限值
/// </summary>
public double? LLAlarmCode { get; set; }
/// <summary>
/// 自定义报警使能
/// </summary>
public bool CustomAlarmEnable { get; set; }
/// <summary>
/// 自定义报警条件约束
/// </summary>
public string? CustomRestrainExpressions { get; set; }
/// <summary>
/// 自定义文本
/// </summary>
public string? CustomAlarmText { get; set; }
/// <summary>
/// 自定义报警条件
/// </summary>
public string? CustomAlarmCode { get; set; }
#endregion
#region
/// <summary>
/// 自定义
/// </summary>
public string? Remark1 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark2 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark3 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark4 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark5 { get; set; }
#endregion
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(RegisterAddress) && string.IsNullOrEmpty(OtherMethod))
{
yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(RegisterAddress), nameof(OtherMethod) });
}
if (HHAlarmEnable && HHAlarmCode == null)
{
yield return new ValidationResult("HHAlarmCode cannot be null when HHAlarmEnable is true", new[] { nameof(HHAlarmCode) });
}
if (HAlarmEnable && HAlarmCode == null)
{
yield return new ValidationResult("HAlarmCode cannot be null when HAlarmEnable is true", new[] { nameof(HAlarmCode) });
}
if (LAlarmEnable && LAlarmCode == null)
{
yield return new ValidationResult("LAlarmCode cannot be null when LAlarmEnable is true", new[] { nameof(LAlarmCode) });
}
if (LLAlarmEnable && LLAlarmCode == null)
{
yield return new ValidationResult("LLAlarmCode cannot be null when LLAlarmEnable is true", new[] { nameof(LLAlarmCode) });
}
if (HHAlarmEnable && HAlarmEnable && HHAlarmCode <= HAlarmCode)
{
yield return new ValidationResult("HHAlarmCode must be greater than HAlarmCode", new[] { nameof(HHAlarmCode), nameof(HAlarmCode) });
}
if (HAlarmEnable && LAlarmEnable && HAlarmCode <= LAlarmCode)
{
yield return new ValidationResult("HAlarmCode must be greater than LAlarmCode", new[] { nameof(HAlarmCode), nameof(LAlarmCode) });
}
if (LAlarmEnable && LLAlarmEnable && LAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("LAlarmCode must be greater than LLAlarmCode", new[] { nameof(LAlarmCode), nameof(LLAlarmCode) });
}
if (HHAlarmEnable && LAlarmEnable && HHAlarmCode <= LAlarmCode)
{
yield return new ValidationResult("HHAlarmCode should be greater than or less than LAlarmCode", new[] { nameof(HHAlarmCode), nameof(LAlarmCode) });
}
if (HHAlarmEnable && LLAlarmEnable && HHAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("HHAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HHAlarmCode), nameof(LLAlarmCode) });
}
if (HAlarmEnable && LLAlarmEnable && HAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("HAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HAlarmCode), nameof(LLAlarmCode) });
}
}
}

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

@@ -90,7 +90,7 @@ public class RuntimeInfoController : ControllerBase, IRpcServer
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task<SqlSugarPagedList<AlarmVariable>> GetRealAlarmList([FromQuery][TouchSocket.WebApi.FromBody] AlarmVariablePageInput input)
{
var realAlarmVariables = await GlobalData.GetCurrentUserRealAlarmVariables().ConfigureAwait(false);
var realAlarmVariables = await GlobalData.GetCurrentUserRealAlarmVariablesAsync().ConfigureAwait(false);
var data = realAlarmVariables
.WhereIF(!input.RegisterAddress.IsNullOrEmpty(), a => a.RegisterAddress == input.RegisterAddress)
@@ -145,7 +145,7 @@ public class RuntimeInfoController : ControllerBase, IRpcServer
public SqlSugarPagedList<PluginInfo> GetPluginInfos([FromQuery][TouchSocket.WebApi.FromBody] PluginInfoPageInput input)
{
//指定关键词搜索为插件FullName
return GlobalData.PluginService.GetList().WhereIF(!input.Name.IsNullOrWhiteSpace(), a => a.Name == input.Name)
return (GlobalData.PluginService.GetPluginList()).WhereIF(!input.Name.IsNullOrWhiteSpace(), a => a.Name == input.Name)
.ToPagedList(input);
}
}

View File

@@ -11,14 +11,19 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ThingsGateway.Admin.Application;
using TouchSocket.Rpc;
namespace ThingsGateway.Gateway.Application;
[Route("api/[controller]/[action]")]
[AllowAnonymous]
[ApiController]
public class TestController : ControllerBase
[TouchSocket.WebApi.Router("/miniapi/[api]/[action]")]
[TouchSocket.WebApi.EnableCors("cors")]
public class TestController : ControllerBase, IRpcServer
{
[HttpGet]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
public void Test()
{
GC.Collect();

View File

@@ -21,6 +21,8 @@ public interface IDBHistoryAlarm
string? Description { get; set; }
string DeviceName { get; set; }
DateTime EventTime { get; set; }
DateTime FinishTime { get; set; }
DateTime ConfirmTime { get; set; }
EventTypeEnum EventType { get; set; }
string Name { get; set; }
string RegisterAddress { get; set; }

View File

@@ -29,7 +29,7 @@ namespace ThingsGateway.Gateway.Application;
/// 采集插件继承实现不同PLC通讯
/// <para></para>
/// </summary>
public abstract class CollectBase : DriverBase, IRpcDriver
public abstract partial class CollectBase : DriverBase, IRpcDriver
{
/// <summary>
/// 插件配置项
@@ -195,7 +195,7 @@ public abstract 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()
@@ -278,11 +278,9 @@ public abstract class CollectBase : DriverBase, IRpcDriver
}
}
#region private
#region
async Task ReadVariableMed(object? state, CancellationToken cancellationToken)
async ValueTask ReadVariableMed(object? state, CancellationToken cancellationToken)
{
if (state is not VariableMethod readVariableMethods) return;
if (Pause)
@@ -348,25 +346,25 @@ public abstract class CollectBase : DriverBase, IRpcDriver
}
#endregion
private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
#region
async Task ReadVariableSource(object? state, CancellationToken cancellationToken)
async ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
{
var readToken = await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false);
if (state is not VariableSourceRead variableSourceRead) return;
if (Pause) return;
if (cancellationToken.IsCancellationRequested) return;
var readToken = await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false);
if (readToken.IsCancellationRequested)
{
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
return;
}
using var allTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, readToken);
var allTokenSource = _linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
var allToken = allTokenSource.Token;
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
@@ -434,13 +432,15 @@ public abstract class CollectBase : DriverBase, IRpcDriver
variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
var time = DateTime.Now;
variableSourceRead.VariableRuntimes.ForEach(a => a.SetValue(null, time, isOnline: false));
foreach (var item in variableSourceRead.VariableRuntimes)
{
item.SetValue(null, time, isOnline: false);
}
}
}
#endregion
#endregion
protected virtual Task TestOnline(object? state, CancellationToken cancellationToken)
{
@@ -722,4 +722,12 @@ public abstract class CollectBase : DriverBase, IRpcDriver
#endregion
protected override async Task DisposeAsync(bool disposing)
{
_linkedCtsCache?.SafeDispose();
if (ReadWriteLock != null)
await ReadWriteLock.SafeDisposeAsync().ConfigureAwait(false);
await base.DisposeAsync(disposing).ConfigureAwait(false);
}
}

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

@@ -0,0 +1,284 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Gateway.Application;
public class AlarmPropertys : IValidatableObject
{
private int alarmDelay;
private int alarmLevel;
private decimal hAlarmCode = 50;
private decimal lAlarmCode = 10;
private decimal hHAlarmCode = 90;
private decimal lLAlarmCode = 0;
private bool boolOpenAlarmEnable;
private bool boolCloseAlarmEnable;
private bool hAlarmEnable;
private bool hHAlarmEnable;
private bool lLAlarmEnable;
private bool lAlarmEnable;
private bool customAlarmEnable;
private string boolOpenRestrainExpressions;
private string boolOpenAlarmText;
private string boolCloseRestrainExpressions;
private string boolCloseAlarmText;
private string hRestrainExpressions;
private string hAlarmText;
private string hHRestrainExpressions;
private string hHAlarmText;
private string lRestrainExpressions;
private string lAlarmText;
private string lLRestrainExpressions;
private string lLAlarmText;
private string customRestrainExpressions;
private string customAlarmText;
private string customAlarmCode;
#region
/// <summary>
/// 报警等级
/// </summary>
[SugarColumn(ColumnDescription = "报警等级")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public int AlarmLevel { get => alarmLevel; set => alarmLevel = value; }
/// <summary>
/// 报警延时
/// </summary>
[SugarColumn(ColumnDescription = "报警延时")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public int AlarmDelay { get => alarmDelay; set => alarmDelay = value; }
/// <summary>
/// 布尔开报警使能
/// </summary>
[SugarColumn(ColumnDescription = "布尔开报警使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool BoolOpenAlarmEnable { get => boolOpenAlarmEnable; set => boolOpenAlarmEnable = value; }
/// <summary>
/// 布尔开报警约束
/// </summary>
[SugarColumn(ColumnDescription = "布尔开报警约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string BoolOpenRestrainExpressions { get => boolOpenRestrainExpressions; set => boolOpenRestrainExpressions = value; }
/// <summary>
/// 布尔开报警文本
/// </summary>
[SugarColumn(ColumnDescription = "布尔开报警文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string BoolOpenAlarmText { get => boolOpenAlarmText; set => boolOpenAlarmText = value; }
/// <summary>
/// 布尔关报警使能
/// </summary>
[SugarColumn(ColumnDescription = "布尔关报警使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool BoolCloseAlarmEnable { get => boolCloseAlarmEnable; set => boolCloseAlarmEnable = value; }
/// <summary>
/// 布尔关报警约束
/// </summary>
[SugarColumn(ColumnDescription = "布尔关报警约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string BoolCloseRestrainExpressions { get => boolCloseRestrainExpressions; set => boolCloseRestrainExpressions = value; }
/// <summary>
/// 布尔关报警文本
/// </summary>
[SugarColumn(ColumnDescription = "布尔关报警文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string BoolCloseAlarmText { get => boolCloseAlarmText; set => boolCloseAlarmText = value; }
/// <summary>
/// 高报使能
/// </summary>
[SugarColumn(ColumnDescription = "高报使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool HAlarmEnable { get => hAlarmEnable; set => hAlarmEnable = value; }
/// <summary>
/// 高报约束
/// </summary>
[SugarColumn(ColumnDescription = "高报约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string HRestrainExpressions { get => hRestrainExpressions; set => hRestrainExpressions = value; }
/// <summary>
/// 高报文本
/// </summary>
[SugarColumn(ColumnDescription = "高报文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string HAlarmText { get => hAlarmText; set => hAlarmText = value; }
/// <summary>
/// 高限值
/// </summary>
[SugarColumn(ColumnDescription = "高限值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public decimal HAlarmCode { get => hAlarmCode; set => hAlarmCode = value; }
/// <summary>
/// 高高报使能
/// </summary>
[SugarColumn(ColumnDescription = "高高报使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool HHAlarmEnable { get => hHAlarmEnable; set => hHAlarmEnable = value; }
/// <summary>
/// 高高报约束
/// </summary>
[SugarColumn(ColumnDescription = "高高报约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string HHRestrainExpressions { get => hHRestrainExpressions; set => hHRestrainExpressions = value; }
/// <summary>
/// 高高报文本
/// </summary>
[SugarColumn(ColumnDescription = "高高报文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string HHAlarmText { get => hHAlarmText; set => hHAlarmText = value; }
/// <summary>
/// 高高限值
/// </summary>
[SugarColumn(ColumnDescription = "高高限值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public decimal HHAlarmCode { get => hHAlarmCode; set => hHAlarmCode = value; }
/// <summary>
/// 低报使能
/// </summary>
[SugarColumn(ColumnDescription = "低报使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool LAlarmEnable { get => lAlarmEnable; set => lAlarmEnable = value; }
/// <summary>
/// 低报约束
/// </summary>
[SugarColumn(ColumnDescription = "低报约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string LRestrainExpressions { get => lRestrainExpressions; set => lRestrainExpressions = value; }
/// <summary>
/// 低报文本
/// </summary>
[SugarColumn(ColumnDescription = "低报文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string LAlarmText { get => lAlarmText; set => lAlarmText = value; }
/// <summary>
/// 低限值
/// </summary>
[SugarColumn(ColumnDescription = "低限值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public decimal LAlarmCode { get => lAlarmCode; set => lAlarmCode = value; }
/// <summary>
/// 低低报使能
/// </summary>
[SugarColumn(ColumnDescription = "低低报使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool LLAlarmEnable { get => lLAlarmEnable; set => lLAlarmEnable = value; }
/// <summary>
/// 低低报约束
/// </summary>
[SugarColumn(ColumnDescription = "低低报约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string LLRestrainExpressions { get => lLRestrainExpressions; set => lLRestrainExpressions = value; }
/// <summary>
/// 低低报文本
/// </summary>
[SugarColumn(ColumnDescription = "低低报文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string LLAlarmText { get => lLAlarmText; set => lLAlarmText = value; }
/// <summary>
/// 低低限值
/// </summary>
[SugarColumn(ColumnDescription = "低低限值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public decimal LLAlarmCode { get => lLAlarmCode; set => lLAlarmCode = value; }
/// <summary>
/// 自定义报警使能
/// </summary>
[SugarColumn(ColumnDescription = "自定义报警使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool CustomAlarmEnable { get => customAlarmEnable; set => customAlarmEnable = value; }
/// <summary>
/// 自定义报警条件约束
/// </summary>
[SugarColumn(ColumnDescription = "自定义报警条件约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string CustomRestrainExpressions { get => customRestrainExpressions; set => customRestrainExpressions = value; }
/// <summary>
/// 自定义文本
/// </summary>
[SugarColumn(ColumnDescription = "自定义文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string CustomAlarmText { get => customAlarmText; set => customAlarmText = value; }
/// <summary>
/// 自定义报警条件
/// </summary>
[SugarColumn(ColumnDescription = "自定义报警条件", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string CustomAlarmCode { get => customAlarmCode; set => customAlarmCode = value; }
#endregion
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (HHAlarmEnable && HAlarmEnable && HHAlarmCode <= HAlarmCode)
{
yield return new ValidationResult("HHAlarmCode must be greater than HAlarmCode", new[] { nameof(HHAlarmCode), nameof(HAlarmCode) });
}
if (HAlarmEnable && LAlarmEnable && HAlarmCode <= LAlarmCode)
{
yield return new ValidationResult("HAlarmCode must be greater than LAlarmCode", new[] { nameof(HAlarmCode), nameof(LAlarmCode) });
}
if (LAlarmEnable && LLAlarmEnable && LAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("LAlarmCode must be greater than LLAlarmCode", new[] { nameof(LAlarmCode), nameof(LLAlarmCode) });
}
if (HHAlarmEnable && LAlarmEnable && HHAlarmCode <= LAlarmCode)
{
yield return new ValidationResult("HHAlarmCode should be greater than or less than LAlarmCode", new[] { nameof(HHAlarmCode), nameof(LAlarmCode) });
}
if (HHAlarmEnable && LLAlarmEnable && HHAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("HHAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HHAlarmCode), nameof(LLAlarmCode) });
}
if (HAlarmEnable && LLAlarmEnable && HAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("HAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HAlarmCode), nameof(LLAlarmCode) });
}
}
}

View File

@@ -17,8 +17,10 @@ namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 后台日志表
///</summary>
#if !Management
[SugarTable("backend_log", TableDescription = "后台日志表")]
[Tenant(SqlSugarConst.DB_Log)]
#endif
public class BackendLog : PrimaryIdEntity
{
/// <summary>

View File

@@ -17,14 +17,15 @@ using TouchSocket.Core;
using TouchSocket.Sockets;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 通道表
/// </summary>
#if !Management
[SugarTable("channel", TableDescription = "通道表")]
[Tenant(SqlSugarConst.DB_Custom)]
[SugarIndex("unique_channel_name", nameof(Channel.Name), OrderByType.Asc, true)]
#endif
public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IBaseEntity
{
/// <summary>

View File

@@ -17,14 +17,15 @@ using System.ComponentModel.DataAnnotations;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 设备表
/// </summary>
#if !Management
[SugarTable("device", TableDescription = "设备表")]
[Tenant(SqlSugarConst.DB_Custom)]
[SugarIndex("unique_device_name", nameof(Device.Name), OrderByType.Asc, true)]
#endif
public class Device : BaseDataEntity, IValidatableObject
{
public override string ToString()
@@ -170,6 +171,7 @@ public class Device : BaseDataEntity, IValidatableObject
#endregion
#if !Management
/// <summary>
/// 导入验证专用
/// </summary>
@@ -184,6 +186,8 @@ public class Device : BaseDataEntity, IValidatableObject
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
public ModelValueValidateForm? ModelValueValidateForm;
#endif
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (RedundantEnable && RedundantDeviceId == null)

View File

@@ -15,8 +15,10 @@ namespace ThingsGateway.Gateway.Application;
/// <summary>
/// Rpc写入日志
///</summary>
#if !Management
[SugarTable("rpc_log", TableDescription = "RPC操作日志")]
[Tenant(SqlSugarConst.DB_Log)]
#endif
public class RpcLog : PrimaryIdEntity
{
/// <summary>

View File

@@ -16,15 +16,16 @@ using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 设备变量表
/// </summary>
#if !Management
[SugarTable("variable", TableDescription = "设备变量表")]
[Tenant(SqlSugarConst.DB_Custom)]
[SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)]
[SugarIndex("unique_deviceid_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)]
#endif
public class Variable : BaseDataEntity, IValidatableObject
{
/// <summary>
@@ -41,13 +42,9 @@ public class Variable : BaseDataEntity, IValidatableObject
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
internal long Row;
private double hAlarmCode = 50;
private double lAlarmCode = 10;
private double hHAlarmCode = 90;
private double lLAlarmCode = 0;
private long deviceId;
private int? arrayLength;
private int alarmDelay;
private ProtectTypeEnum protectType = ProtectTypeEnum.ReadWrite;
private DataTypeEnum dataType = DataTypeEnum.Int16;
@@ -61,13 +58,6 @@ public class Variable : BaseDataEntity, IValidatableObject
public bool DynamicVariable;
private bool rpcWriteEnable = true;
private bool saveValue = false;
private bool boolOpenAlarmEnable;
private bool boolCloseAlarmEnable;
private bool hAlarmEnable;
private bool hHAlarmEnable;
private bool lLAlarmEnable;
private bool lAlarmEnable;
private bool customAlarmEnable;
private bool businessGroupUpdateTrigger = true;
private bool rpcWriteCheck;
@@ -82,29 +72,17 @@ public class Variable : BaseDataEntity, IValidatableObject
private string otherMethod;
private string readExpressions;
private string writeExpressions;
private string boolOpenRestrainExpressions;
private string boolOpenAlarmText;
private string boolCloseRestrainExpressions;
private string boolCloseAlarmText;
private string hRestrainExpressions;
private string hAlarmText;
private Dictionary<long, Dictionary<string, string>>? variablePropertys;
private string hHRestrainExpressions;
private string hHAlarmText;
private string lRestrainExpressions;
private string lAlarmText;
private string lLRestrainExpressions;
private string lLAlarmText;
private string customRestrainExpressions;
private string customAlarmText;
private string customAlarmCode;
private Dictionary<long, Dictionary<string, string>>? variablePropertys;
private string remark1;
private string remark2;
private string remark3;
private string remark4;
private string remark5;
[MapperIgnore]
public ValidateForm AlarmPropertysValidateForm;
/// <summary>
/// 变量额外属性Json
/// </summary>
@@ -273,197 +251,15 @@ public class Variable : BaseDataEntity, IValidatableObject
[AutoGenerateColumn(Ignore = true)]
public Dictionary<long, Dictionary<string, string>>? VariablePropertys { get => variablePropertys; set => variablePropertys = value; }
#region
/// <summary>
/// 报警延时
/// </summary>
[SugarColumn(ColumnDescription = "报警延时")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public int AlarmDelay { get => alarmDelay; set => alarmDelay = value; }
/// <summary>
/// 布尔开报警使能
/// 变量报警属性Json
/// </summary>
[SugarColumn(ColumnDescription = "布尔开报警使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool BoolOpenAlarmEnable { get => boolOpenAlarmEnable; set => boolOpenAlarmEnable = value; }
[SugarColumn(IsJson = true, ColumnDataType = StaticConfig.CodeFirst_BigString, ColumnDescription = "报警属性Json", IsNullable = true)]
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public AlarmPropertys? AlarmPropertys { get; set; }
/// <summary>
/// 布尔开报警约束
/// </summary>
[SugarColumn(ColumnDescription = "布尔开报警约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string BoolOpenRestrainExpressions { get => boolOpenRestrainExpressions; set => boolOpenRestrainExpressions = value; }
/// <summary>
/// 布尔开报警文本
/// </summary>
[SugarColumn(ColumnDescription = "布尔开报警文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string BoolOpenAlarmText { get => boolOpenAlarmText; set => boolOpenAlarmText = value; }
/// <summary>
/// 布尔关报警使能
/// </summary>
[SugarColumn(ColumnDescription = "布尔关报警使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool BoolCloseAlarmEnable { get => boolCloseAlarmEnable; set => boolCloseAlarmEnable = value; }
/// <summary>
/// 布尔关报警约束
/// </summary>
[SugarColumn(ColumnDescription = "布尔关报警约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string BoolCloseRestrainExpressions { get => boolCloseRestrainExpressions; set => boolCloseRestrainExpressions = value; }
/// <summary>
/// 布尔关报警文本
/// </summary>
[SugarColumn(ColumnDescription = "布尔关报警文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string BoolCloseAlarmText { get => boolCloseAlarmText; set => boolCloseAlarmText = value; }
/// <summary>
/// 高报使能
/// </summary>
[SugarColumn(ColumnDescription = "高报使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool HAlarmEnable { get => hAlarmEnable; set => hAlarmEnable = value; }
/// <summary>
/// 高报约束
/// </summary>
[SugarColumn(ColumnDescription = "高报约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string HRestrainExpressions { get => hRestrainExpressions; set => hRestrainExpressions = value; }
/// <summary>
/// 高报文本
/// </summary>
[SugarColumn(ColumnDescription = "高报文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string HAlarmText { get => hAlarmText; set => hAlarmText = value; }
/// <summary>
/// 高限值
/// </summary>
[SugarColumn(ColumnDescription = "高限值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public double HAlarmCode { get => hAlarmCode; set => hAlarmCode = value; }
/// <summary>
/// 高高报使能
/// </summary>
[SugarColumn(ColumnDescription = "高高报使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool HHAlarmEnable { get => hHAlarmEnable; set => hHAlarmEnable = value; }
/// <summary>
/// 高高报约束
/// </summary>
[SugarColumn(ColumnDescription = "高高报约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string HHRestrainExpressions { get => hHRestrainExpressions; set => hHRestrainExpressions = value; }
/// <summary>
/// 高高报文本
/// </summary>
[SugarColumn(ColumnDescription = "高高报文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string HHAlarmText { get => hHAlarmText; set => hHAlarmText = value; }
/// <summary>
/// 高高限值
/// </summary>
[SugarColumn(ColumnDescription = "高高限值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public double HHAlarmCode { get => hHAlarmCode; set => hHAlarmCode = value; }
/// <summary>
/// 低报使能
/// </summary>
[SugarColumn(ColumnDescription = "低报使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool LAlarmEnable { get => lAlarmEnable; set => lAlarmEnable = value; }
/// <summary>
/// 低报约束
/// </summary>
[SugarColumn(ColumnDescription = "低报约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string LRestrainExpressions { get => lRestrainExpressions; set => lRestrainExpressions = value; }
/// <summary>
/// 低报文本
/// </summary>
[SugarColumn(ColumnDescription = "低报文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string LAlarmText { get => lAlarmText; set => lAlarmText = value; }
/// <summary>
/// 低限值
/// </summary>
[SugarColumn(ColumnDescription = "低限值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public double LAlarmCode { get => lAlarmCode; set => lAlarmCode = value; }
/// <summary>
/// 低低报使能
/// </summary>
[SugarColumn(ColumnDescription = "低低报使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool LLAlarmEnable { get => lLAlarmEnable; set => lLAlarmEnable = value; }
/// <summary>
/// 低低报约束
/// </summary>
[SugarColumn(ColumnDescription = "低低报约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string LLRestrainExpressions { get => lLRestrainExpressions; set => lLRestrainExpressions = value; }
/// <summary>
/// 低低报文本
/// </summary>
[SugarColumn(ColumnDescription = "低低报文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string LLAlarmText { get => lLAlarmText; set => lLAlarmText = value; }
/// <summary>
/// 低低限值
/// </summary>
[SugarColumn(ColumnDescription = "低低限值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public double LLAlarmCode { get => lLAlarmCode; set => lLAlarmCode = value; }
/// <summary>
/// 自定义报警使能
/// </summary>
[SugarColumn(ColumnDescription = "自定义报警使能")]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool CustomAlarmEnable { get => customAlarmEnable; set => customAlarmEnable = value; }
/// <summary>
/// 自定义报警条件约束
/// </summary>
[SugarColumn(ColumnDescription = "自定义报警条件约束", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string CustomRestrainExpressions { get => customRestrainExpressions; set => customRestrainExpressions = value; }
/// <summary>
/// 自定义文本
/// </summary>
[SugarColumn(ColumnDescription = "自定义文本", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string CustomAlarmText { get => customAlarmText; set => customAlarmText = value; }
/// <summary>
/// 自定义报警条件
/// </summary>
[SugarColumn(ColumnDescription = "自定义报警条件", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public string CustomAlarmCode { get => customAlarmCode; set => customAlarmCode = value; }
#endregion
#region
@@ -511,30 +307,6 @@ public class Variable : BaseDataEntity, IValidatableObject
yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(OtherMethod), nameof(RegisterAddress) });
}
if (HHAlarmEnable && HAlarmEnable && HHAlarmCode <= HAlarmCode)
{
yield return new ValidationResult("HHAlarmCode must be greater than HAlarmCode", new[] { nameof(HHAlarmCode), nameof(HAlarmCode) });
}
if (HAlarmEnable && LAlarmEnable && HAlarmCode <= LAlarmCode)
{
yield return new ValidationResult("HAlarmCode must be greater than LAlarmCode", new[] { nameof(HAlarmCode), nameof(LAlarmCode) });
}
if (LAlarmEnable && LLAlarmEnable && LAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("LAlarmCode must be greater than LLAlarmCode", new[] { nameof(LAlarmCode), nameof(LLAlarmCode) });
}
if (HHAlarmEnable && LAlarmEnable && HHAlarmCode <= LAlarmCode)
{
yield return new ValidationResult("HHAlarmCode should be greater than or less than LAlarmCode", new[] { nameof(HHAlarmCode), nameof(LAlarmCode) });
}
if (HHAlarmEnable && LLAlarmEnable && HHAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("HHAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HHAlarmCode), nameof(LLAlarmCode) });
}
if (HAlarmEnable && LLAlarmEnable && HAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("HAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HAlarmCode), nameof(LLAlarmCode) });
}
}
}

View File

@@ -39,4 +39,16 @@ public enum EventTypeEnum
/// 准备恢复
/// </summary>
PrepareFinish,
/// <summary>
/// 报警确认并恢复
/// </summary>
ConfirmAndFinish,
/// <summary>
/// 重启默认
/// </summary>
Restart,
}

View File

@@ -8,7 +8,6 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
namespace Microsoft.Extensions.DependencyInjection;
@@ -19,30 +18,19 @@ namespace Microsoft.Extensions.DependencyInjection;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class ServiceCollectionHostedServiceExtensions
{
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
/// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
/// <returns>The original <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddGatewayHostedService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
{
services.AddSingleton<THostedService>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>(seriveProvider => seriveProvider.GetService<THostedService>()));
return services;
}
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
public static IServiceCollection AddGatewayHostedService<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
where TService : class, IHostedService
where TService : class
where THostedService : class, IHostedService, TService
{
services.AddSingleton(typeof(TService), typeof(THostedService));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, TService>(seriveProvider => seriveProvider.GetService<TService>()));
services.AddSingleton<THostedService>();
services.AddHostedService<THostedService>(a => a.GetService<THostedService>());
services.AddSingleton<TService>(a => a.GetService<THostedService>());
return services;
}
}

View File

@@ -83,7 +83,7 @@ public static class GlobalData
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
}
public static async Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariables()
public static async Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync()
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
return RealAlarmIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
@@ -177,6 +177,15 @@ public static class GlobalData
}
return GlobalData.ChannelThreadManage.DeviceThreadManages.TryGetValue(deviceRuntime.ChannelId, out deviceThreadManage);
}
public static IChannelThreadManage GetChannelThreadManage(ChannelRuntime channelRuntime)
{
if (channelRuntime.DeviceThreadManage?.ChannelThreadManage != null)
return channelRuntime.DeviceThreadManage.ChannelThreadManage;
else
return GlobalData.ChannelThreadManage;
}
public static Dictionary<IDeviceThreadManage, List<DeviceRuntime>> GetDeviceThreadManages(IEnumerable<DeviceRuntime> deviceRuntimes)
{
Dictionary<IDeviceThreadManage, List<DeviceRuntime>> deviceThreadManages = new();

View File

@@ -1,6 +1,32 @@
{
"ThingsGateway.Management.Application.ManagementExportString": {
"ThingsGateway.Gateway.Application.DefaultDiagram": {
"ManagementConfigName": "ManagementConfigName"
},
"ThingsGateway.Management.Application.ManagementConfig": {
"Name": "Name",
"ServerUri": "ServerUri",
"Enable": "Enable",
"IsServer": "IsServer",
"VerifyToken": "VerifyToken",
"HeartbeatInterval": "HeartbeatInterval",
"ImportNullError": "Unable to recognize"
},
"ThingsGateway.Management.Application.UpdateZipFile": {
"AppName": "AppName",
"Architecture": "Architecture",
"DotNetVersion": "DotNetVersion",
"FilePath": "FilePath",
"FileSize": "FileSize",
"MinimumCompatibleVersion": "MinimumCompatibleVersion",
"OSPlatform": "OSPlatform",
"Version": "Version"
},
"ThingsGateway.Gateway.Application.INode": {
"Actuator": "Actuator",
"AlarmChangedTriggerNode": "AlarmStateTrigger",
@@ -53,11 +79,7 @@
},
"ThingsGateway.Management.AutoUpdateController": {
"AutoUpdateController": "AutoUpdate",
"Update": "Update"
},
"ThingsGateway.Management.RedundancyHostedService": {
"ThingsGateway.Gateway.Application.RedundancyHostedService": {
"ErrorSynchronizingData": "Synchronize data to standby site error",
"RedundancyDisable": "Redundant gateway site not enabled",
"RedundancyDup": "Redundant station settings duplicated",
@@ -67,10 +89,10 @@
"SwitchNormalState": "Local machine (primary site) will switch to normal state",
"SwitchSlaveState": "Master site has recovered, local machine (standby) will switch to standby state"
},
"ThingsGateway.Management.RedundancyOptions": {
"ThingsGateway.Gateway.Application.RedundancyOptions": {
"Confirm": "Confirm switching to redundant state",
"Enable": "Enable Dual-Machine Redundancy",
"ForcedSync": "Forced Synchronous",
"RedundancyForcedSync": "Forced Synchronous",
"ForcedSyncWarning": "Forcing synchronization will generate database configuration information.Are you sure you want to continue?",
"HeartbeatInterval": "Heartbeat Interval",
"IsMaster": "IsMaster",
@@ -86,13 +108,13 @@
"SyncInterval": "Data Synchronization Interval",
"VerifyToken": "Verification Token"
},
"ThingsGateway.Management.RedundancyService": {
"ThingsGateway.Gateway.Application.RedundancyService": {
"EditRedundancyOption": "EditRedundancyOption"
},
"ThingsGateway.Management.UpdateZipFileHostedService": {
"ThingsGateway.Gateway.Application.UpdateZipFileService": {
"Update": "New version detected"
},
"ThingsGateway.Upgrade.UpdateZipFile": {
"ThingsGateway.Gateway.Application.UpdateZipFile": {
"AppName": "AppName",
"Architecture": "Architecture",
"DotNetVersion": "DotNetVersion",
@@ -106,6 +128,7 @@
"ThingsGateway.Gateway.Application.AlarmVariable": {
"AlarmCode": "AlarmCode",
"AlarmDelay": "AlarmDelay",
"AlarmLevel": "AlarmLevel",
"AlarmEnable": "AlarmEnable",
"AlarmLimit": "AlarmLimit",
"AlarmText": "AlarmText",
@@ -132,6 +155,8 @@
"DeviceName": "DeviceName",
"Enable": "Enable",
"EventTime": "EventTime",
"ConfirmTime": "ConfirmTime",
"FinishTime": "FinishTime",
"EventType": "EventType",
"HAlarmCode": "HAlarmCode",
"HAlarmEnable": "HAlarmEnable",
@@ -296,7 +321,8 @@
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"RetryCount": "RetryCount",
"DutyCycle": "DutyCycle"
"DutyCycle": "DutyCycle",
"WritePriority": "WritePriority"
},
"ThingsGateway.Gateway.Application.ControlController": {
"BatchSaveChannelAsync": "BatchSaveChannel",
@@ -371,10 +397,11 @@
"ExpireTime": "ExpireTime {0}",
"Unauthorized": "Unauthorized"
},
"ThingsGateway.Gateway.Application.ExportString": {
"ThingsGateway.Gateway.Application.GatewayExportString": {
"BusinessDeviceName": "BusinessDevice",
"ChannelName": "Channel",
"DeviceName": "Device",
"AlarmName": "Alarm",
"RedundantDeviceName": "Redundant Device",
"VariableName": "Variable"
},
@@ -436,24 +463,16 @@
},
"ThingsGateway.Gateway.Application.Variable": {
"AddressOrOtherMethodNotNull": "Variable address or special method cannot be empty at the same time",
"AlarmDelay": "AlarmDelay",
"ArrayLength": "ArrayLength",
"BoolCloseAlarmEnable": "BoolCloseAlarmEnable",
"BoolCloseAlarmText": "BoolCloseAlarmText",
"BoolCloseRestrainExpressions": "BoolCloseRestrainExpressions",
"BoolOpenAlarmEnable": "BoolOpenAlarmEnable",
"BoolOpenAlarmText": "BoolOpenAlarmText",
"BoolOpenRestrainExpressions": "BoolOpenRestrainExpressions",
"BusinessGroup": "BusinessGroup",
"BusinessGroupUpdateTrigger": "BusinessGroupUpdateTrigger",
"RpcWriteCheck": "RpcWriteCheck",
"ClearVariable": "Clear Variable",
"CollectGroup": "CollectGroup",
"CopyVariable": "Copy Variable",
"CustomAlarmCode": "CustomAlarmCode",
"CustomAlarmEnable": "CustomAlarmEnable",
"CustomAlarmText": "CustomAlarmText",
"CustomRestrainExpressions": "CustomRestrainExpressions",
"DataType": "DataType",
"DeleteVariable": "Delete Variable",
"Description": "Description",
@@ -464,26 +483,14 @@
"DynamicVariable": "DynamicVariable",
"Enable": "Enable",
"ExportVariable": "Export Variable",
"HAlarmCode": "HAlarmCode",
"HAlarmEnable": "HAlarmEnable",
"HAlarmText": "HAlarmText",
"HHAlarmCode": "HHAlarmCode",
"HHAlarmEnable": "HHAlarmEnable",
"HHAlarmText": "HHAlarmText",
"HHRestrainExpressions": "HHRestrainExpressions",
"HRestrainExpressions": "HRestrainExpressions",
"ImportVariable": "Import Variable",
"InitValue": "InitValue",
"IntervalTime": "IntervalTime",
"IntervalTime.MinValue": "{0} value is too small",
"LAlarmCode": "LAlarmCode",
"LAlarmEnable": "LAlarmEnable",
"LAlarmText": "LAlarmText",
"LLAlarmCode": "LLAlarmCode",
"LLAlarmEnable": "LLAlarmEnable",
"LLAlarmText": "LLAlarmText",
"LLRestrainExpressions": "LLRestrainExpressions",
"LRestrainExpressions": "LRestrainExpressions",
"Name": "Name",
"Name.Required": "{0} cannot be empty",
"NameDump": "Duplicate variable name {0}",
@@ -505,6 +512,38 @@
"VariableNotNull": "Variable name does not exist",
"WriteExpressions": "WriteExpressions"
},
"ThingsGateway.Gateway.Application.AlarmPropertys": {
"AlarmDelay": "AlarmDelay",
"AlarmLevel": "AlarmLevel",
"BoolCloseAlarmEnable": "BoolCloseAlarmEnable",
"BoolCloseAlarmText": "BoolCloseAlarmText",
"BoolCloseRestrainExpressions": "BoolCloseRestrainExpressions",
"BoolOpenAlarmEnable": "BoolOpenAlarmEnable",
"BoolOpenAlarmText": "BoolOpenAlarmText",
"BoolOpenRestrainExpressions": "BoolOpenRestrainExpressions",
"CustomAlarmCode": "CustomAlarmCode",
"CustomAlarmEnable": "CustomAlarmEnable",
"CustomAlarmText": "CustomAlarmText",
"CustomRestrainExpressions": "CustomRestrainExpressions",
"HAlarmCode": "HAlarmCode",
"HAlarmEnable": "HAlarmEnable",
"HAlarmText": "HAlarmText",
"HHAlarmCode": "HHAlarmCode",
"HHAlarmEnable": "HHAlarmEnable",
"HHAlarmText": "HHAlarmText",
"HHRestrainExpressions": "HHRestrainExpressions",
"HRestrainExpressions": "HRestrainExpressions",
"LAlarmCode": "LAlarmCode",
"LAlarmEnable": "LAlarmEnable",
"LAlarmText": "LAlarmText",
"LLAlarmCode": "LLAlarmCode",
"LLAlarmEnable": "LLAlarmEnable",
"LLAlarmText": "LLAlarmText",
"LLRestrainExpressions": "LLRestrainExpressions",
"LRestrainExpressions": "LRestrainExpressions"
},
"ThingsGateway.Gateway.Application.VariableRuntime": {
"AlarmCode": "AlarmCode",
"AlarmEnable": "AlarmEnable",
@@ -519,6 +558,8 @@
"DeviceName": "DeviceName",
"DynamicVariable": "DynamicVariable",
"EventTime": "EventTime",
"ConfirmTime": "ConfirmTime",
"FinishTime": "FinishTime",
"EventType": "EventType",
"IntervalTime.MinValue": "{0} value is too small",
"IsOnline": "IsOnline",

View File

@@ -1,6 +1,34 @@
{
"ThingsGateway.Management.Application.ManagementExportString": {
"ThingsGateway.Gateway.Application.DefaultDiagram": {
"ManagementConfigName": "通讯配置"
},
"ThingsGateway.Management.Application.ManagementConfig": {
"Name": "名称",
"ServerUri": "服务端Url",
"Enable": "启用",
"IsServer": "服务端",
"VerifyToken": "验证令牌",
"HeartbeatInterval": "心跳间隔",
"ImportNullError": "无法识别"
},
"ThingsGateway.Management.Application.UpdateZipFile": {
"AppName": "名称",
"Architecture": "架构",
"DotNetVersion": ".net版本",
"FilePath": "文件路径",
"FileSize": "文件大小",
"MinimumCompatibleVersion": "最小兼容版本",
"OSPlatform": "系统版本",
"Version": "版本"
},
"ThingsGateway.Gateway.Application.INode": {
"Actuator": "执行",
"AlarmChangedTriggerNode": "报警状态触发器",
@@ -51,11 +79,8 @@
"RulesId": "名称"
},
"ThingsGateway.Management.AutoUpdateController": {
"AutoUpdateController": "程序更新",
"Update": "更新"
},
"ThingsGateway.Management.RedundancyHostedService": {
"ThingsGateway.Gateway.Application.RedundancyHostedService": {
"ErrorSynchronizingData": "同步数据到从站错误",
"RedundancyDisable": "不启用网关冗余站点",
"RedundancyDup": "主备站设置重复",
@@ -65,10 +90,10 @@
"SwitchNormalState": "本机(主站)将切换到正常状态",
"SwitchSlaveState": "主站已恢复,本机(从站)将切换到备用状态"
},
"ThingsGateway.Management.RedundancyOptions": {
"ThingsGateway.Gateway.Application.RedundancyOptions": {
"Confirm": "确认切换冗余状态",
"Enable": "启用双机冗余",
"ForcedSync": "强制同步",
"RedundancyForcedSync": "强制同步",
"ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?",
"HeartbeatInterval": "心跳间隔",
"IsMaster": "是否为主站",
@@ -82,15 +107,15 @@
"Status": "当前站点状态",
"Switch": "切换",
"SyncInterval": "数据同步间隔",
"VerifyToken": "Token"
"VerifyToken": "验证令牌"
},
"ThingsGateway.Management.RedundancyService": {
"ThingsGateway.Gateway.Application.RedundancyService": {
"EditRedundancyOption": "修改网关冗余配置"
},
"ThingsGateway.Management.UpdateZipFileHostedService": {
"ThingsGateway.Gateway.Application.UpdateZipFileService": {
"Update": "检测到新版本"
},
"ThingsGateway.Upgrade.UpdateZipFile": {
"ThingsGateway.Gateway.Application.UpdateZipFile": {
"AppName": "名称",
"Architecture": "架构",
"DotNetVersion": ".net版本",
@@ -105,6 +130,7 @@
"ThingsGateway.Gateway.Application.AlarmVariable": {
"AlarmCode": "报警值",
"AlarmDelay": "报警延时",
"AlarmLevel": "报警等级",
"AlarmEnable": "报警使能",
"AlarmLimit": "报警限值",
"AlarmText": "报警文本",
@@ -131,6 +157,8 @@
"DeviceName": "设备名称",
"Enable": "变量使能",
"EventTime": "事件时间",
"ConfirmTime": "确认时间",
"FinishTime": "恢复时间",
"EventType": "事件类型",
"HAlarmCode": "高限值",
"HAlarmEnable": "高报使能",
@@ -295,7 +323,8 @@
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"RetryCount": "失败重试次数",
"DutyCycle": "占空比"
"DutyCycle": "占空比",
"WritePriority": "写优先"
},
"ThingsGateway.Gateway.Application.ControlController": {
"BatchSaveChannelAsync": "保存通道",
@@ -372,10 +401,11 @@
"ExpireTime": "过期时间 {0}",
"Unauthorized": "未授权"
},
"ThingsGateway.Gateway.Application.ExportString": {
"ThingsGateway.Gateway.Application.GatewayExportString": {
"BusinessDeviceName": "业务设备",
"ChannelName": "通道",
"DeviceName": "设备",
"AlarmName": "报警",
"RedundantDeviceName": "冗余设备",
"VariableName": "变量"
},
@@ -438,6 +468,7 @@
"ThingsGateway.Gateway.Application.Variable": {
"AddressOrOtherMethodNotNull": " 变量地址或特殊方法不能同时为空 ",
"AlarmDelay": "报警延时",
"AlarmLevel": "报警等级",
"ArrayLength": "数组长度",
"BoolCloseAlarmEnable": "布尔关报警使能",
"BoolCloseAlarmText": "布尔关报警文本",
@@ -506,6 +537,40 @@
"VariableNotNull": "变量名称不存在",
"WriteExpressions": "写入表达式"
},
"ThingsGateway.Gateway.Application.AlarmPropertys": {
"AlarmDelay": "报警延时",
"AlarmLevel": "报警等级",
"BoolCloseAlarmEnable": "布尔关报警使能",
"BoolCloseAlarmText": "布尔关报警文本",
"BoolCloseRestrainExpressions": "布尔关报警约束",
"BoolOpenAlarmEnable": "布尔开报警使能",
"BoolOpenAlarmText": "布尔开报警文本",
"BoolOpenRestrainExpressions": "布尔开报警约束",
"CustomAlarmCode": "自定义报警限值",
"CustomAlarmEnable": "自定义报警使能",
"CustomAlarmText": "自定义报警文本",
"CustomRestrainExpressions": "自定义报警约束",
"HAlarmCode": "高限值",
"HAlarmEnable": "高报使能",
"HAlarmText": "高报文本",
"HHAlarmCode": "高高限值",
"HHAlarmEnable": "高高报使能",
"HHAlarmText": "高高报文本",
"HHRestrainExpressions": "高高报约束",
"HRestrainExpressions": "高报约束",
"LAlarmCode": "低限值",
"LAlarmEnable": "低报使能",
"LAlarmText": "低报文本",
"LLAlarmCode": "低低限值",
"LLAlarmEnable": "低低报使能",
"LLAlarmText": "低低报文本",
"LLRestrainExpressions": "低低报约束",
"LRestrainExpressions": "低报约束"
},
"ThingsGateway.Gateway.Application.VariableRuntime": {
"AlarmCode": "报警值",
"AlarmEnable": "报警使能",
@@ -520,6 +585,8 @@
"DeviceName": "设备名称",
"DynamicVariable": "动态变量",
"EventTime": "事件时间",
"ConfirmTime": "确认时间",
"FinishTime": "恢复时间",
"EventType": "事件类型",
"IntervalTime.MinValue": " {0} 值太小",
"IsOnline": "在线",

View File

@@ -10,15 +10,25 @@
using Riok.Mapperly.Abstractions;
using ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
public static partial class GatewayMapper
{
[MapProperty($"{nameof(VariableRuntime.AlarmPropertys)}.{nameof(AlarmPropertys.AlarmLevel)}", nameof(AlarmVariable.AlarmLevel))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.AlarmCode)}", nameof(AlarmVariable.AlarmCode))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.AlarmLimit)}", nameof(AlarmVariable.AlarmLimit))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.AlarmText)}", nameof(AlarmVariable.AlarmText))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.RecoveryCode)}", nameof(AlarmVariable.RecoveryCode))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.AlarmTime)}", nameof(AlarmVariable.AlarmTime))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.FinishTime)}", nameof(AlarmVariable.FinishTime))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.ConfirmTime)}", nameof(AlarmVariable.ConfirmTime))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.EventTime)}", nameof(AlarmVariable.EventTime))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.AlarmType)}", nameof(AlarmVariable.AlarmType))]
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.EventType)}", nameof(AlarmVariable.EventType))]
public static partial AlarmVariable AdaptAlarmVariable(this VariableRuntime src);
public static partial VariableDataWithValue AdaptVariableDataWithValue(this VariableBasicData src);
public static partial VariableDataWithValue AdaptVariableDataWithValue(this VariableRuntime src);
public static partial AlarmVariable AdaptAlarmVariable(this VariableRuntime src);
public static partial DeviceBasicData AdaptDeviceBasicData(this DeviceRuntime src);
public static partial IEnumerable<DeviceBasicData> AdaptIEnumerableDeviceBasicData(this IEnumerable<DeviceRuntime> src);
public static partial List<DeviceBasicData> AdaptListDeviceBasicData(this IEnumerable<DeviceRuntime> src);
@@ -30,6 +40,7 @@ public static partial class GatewayMapper
[MapProperty(nameof(Variable.InitValue), nameof(VariableRuntime.Value))]
public static partial VariableRuntime AdaptVariableRuntime(this Variable src);
public static partial List<Variable> AdaptListVariable(this IEnumerable<Variable> src);
public static partial DeviceRuntime AdaptDeviceRuntime(this Device src);
@@ -51,9 +62,7 @@ public static partial class GatewayMapper
public static partial RedundancyOptions AdaptRedundancyOptions(this RedundancyOptions src);
public static partial List<DeviceDataWithValue> AdaptListDeviceDataWithValue(this IEnumerable<DeviceRuntime> src);
public static partial List<Channel> AdaptListChannel(this List<ChannelInput> src);
public static partial List<Device> AdaptListDevice(this List<DeviceInput> src);
public static partial List<Variable> AdaptListVariable(this List<VariableInput> src);
public static partial Channel AdaptChannel(this Channel src);
public static partial Device AdaptDevice(this Device src);
public static partial Variable AdaptVariable(this Variable src);

View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
namespace ThingsGateway.Gateway.Application;
public partial class AlarmRuntimePropertys
{
/// <summary>
/// 事件类型
/// </summary>
[AutoGenerateColumn(Visible = false)]
public EventTypeEnum? EventType { get; set; }
/// <summary>
/// 报警类型
/// </summary>
[AutoGenerateColumn(Visible = false)]
public AlarmTypeEnum? AlarmType { get; set; }
/// <summary>
/// 报警值
/// </summary>
[AutoGenerateColumn(Visible = false)]
public string AlarmCode { get; set; }
/// <summary>
/// 恢复值
/// </summary>
[AutoGenerateColumn(Visible = false)]
public string RecoveryCode { get; set; }
/// <summary>
/// 事件时间
/// </summary>
[AutoGenerateColumn(Ignore = true)]
internal DateTime? PrepareAlarmEventTime { get; set; }
/// <summary>
/// 事件时间
/// </summary>
[AutoGenerateColumn(Ignore = true)]
internal DateTime? PrepareFinishEventTime { get; set; }
/// <summary>
/// 报警时间
/// </summary>
[AutoGenerateColumn(Visible = false)]
public DateTime AlarmTime { get; set; } = DateTime.UnixEpoch.ToLocalTime();
[AutoGenerateColumn(Visible = false)]
public DateTime FinishTime { get; set; } = DateTime.UnixEpoch.ToLocalTime();
[AutoGenerateColumn(Visible = false)]
public DateTime ConfirmTime { get; set; } = DateTime.UnixEpoch.ToLocalTime();
/// <summary>
/// 报警限值
/// </summary>
[AutoGenerateColumn(Visible = false)]
public string AlarmLimit { get; set; }
/// <summary>
/// 报警文本
/// </summary>
[AutoGenerateColumn(Visible = false)]
public string AlarmText { get; set; }
[AutoGenerateColumn(Visible = false)]
public DateTime EventTime { get; set; } = DateTime.UnixEpoch.ToLocalTime();
internal object AlarmLockObject = new();
#if !Management
internal bool AlarmConfirm;
#endif
}

View File

@@ -63,72 +63,86 @@ public class AlarmVariable : PrimaryIdEntity, IDBHistoryAlarm
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public DataTypeEnum DataType { get; set; }
/// <inheritdoc cref="VariableRuntime.AlarmCode"/>
/// <inheritdoc cref="AlarmPropertys.AlarmLevel"/>
[SugarColumn(ColumnDescription = "报警等级", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public int AlarmLevel { get; set; }
/// <inheritdoc cref="AlarmRuntimePropertys.AlarmCode"/>
[SugarColumn(ColumnDescription = "报警值", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public string AlarmCode { get; set; }
/// <inheritdoc cref="VariableRuntime.AlarmLimit"/>
/// <inheritdoc cref="AlarmRuntimePropertys.AlarmLimit"/>
[SugarColumn(ColumnDescription = "报警限值", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public string AlarmLimit { get; set; }
/// <inheritdoc cref="VariableRuntime.AlarmText"/>
/// <inheritdoc cref="AlarmRuntimePropertys.AlarmText"/>
[SugarColumn(ColumnDescription = "报警文本", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public string? AlarmText { get; set; }
/// <inheritdoc cref="VariableRuntime.RecoveryCode"/>
/// <inheritdoc cref="AlarmRuntimePropertys.RecoveryCode"/>
[SugarColumn(ColumnDescription = "恢复值", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public string RecoveryCode { get; set; }
/// <inheritdoc cref="VariableRuntime.AlarmTime"/>
/// <inheritdoc cref="AlarmRuntimePropertys.AlarmTime"/>
[SugarColumn(ColumnDescription = "报警时间", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public DateTime AlarmTime { get; set; }
/// <inheritdoc cref="VariableRuntime.EventTime"/>
/// <inheritdoc cref="AlarmRuntimePropertys.EventTime"/>
[SugarColumn(ColumnDescription = "事件时间", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[TimeDbSplitField(DateType.Month)]
public DateTime EventTime { get; set; }
/// <summary>
/// 报警类型
/// </summary>
/// <inheritdoc cref="AlarmRuntimePropertys.FinishTime"/>
[SugarColumn(ColumnDescription = "恢复时间", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[TimeDbSplitField(DateType.Month)]
public DateTime FinishTime { get; set; }
/// <inheritdoc cref="AlarmRuntimePropertys.ConfirmTime"/>
[SugarColumn(ColumnDescription = "确认时间", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[TimeDbSplitField(DateType.Month)]
public DateTime ConfirmTime { get; set; }
/// <inheritdoc cref="AlarmRuntimePropertys.AlarmType"/>
[SugarColumn(ColumnDescription = "报警类型", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public AlarmTypeEnum? AlarmType { get; set; }
/// <summary>
/// 事件类型
/// </summary>
/// <inheritdoc cref="AlarmRuntimePropertys.EventType"/>
[SugarColumn(ColumnDescription = "事件类型", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public EventTypeEnum EventType { get; set; }
/// <inheritdoc cref="Device.Remark1"/>
/// <inheritdoc cref="Variable.Remark1"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string Remark1 { get; set; }
/// <inheritdoc cref="Device.Remark2"/>
/// <inheritdoc cref="Variable.Remark2"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string Remark2 { get; set; }
/// <inheritdoc cref="Device.Remark3"/>
/// <inheritdoc cref="Variable.Remark3"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string Remark3 { get; set; }
/// <inheritdoc cref="Device.Remark4"/>
/// <inheritdoc cref="Variable.Remark4"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string Remark4 { get; set; }
/// <inheritdoc cref="Device.Remark5"/>
/// <inheritdoc cref="Variable.Remark5"/>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
public string Remark5 { get; set; }

View File

@@ -8,7 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Gateway.Application;
public enum ChannelDevicePluginTypeEnum
{
PluginType,

View File

@@ -23,27 +23,15 @@ namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 业务设备运行状态
/// </summary>
public class ChannelRuntime : Channel, IChannelOptions, IDisposable
public class ChannelRuntime : Channel
#if !Management
,
IChannelOptions,
IDisposable
#endif
{
/// <summary>
/// 插件信息
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public PluginInfo? PluginInfo { get; set; }
/// <summary>
/// 是否采集
/// </summary>
public PluginTypeEnum? PluginType => PluginInfo?.PluginType;
/// <summary>
/// 是否采集
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public bool? IsCollect => PluginInfo == null ? null : PluginInfo?.PluginType == PluginTypeEnum.Collect;
#if !Management
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
@@ -105,19 +93,72 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
[Newtonsoft.Json.JsonIgnore]
public int? DeviceRuntimeCount => DeviceRuntimes?.Count;
public bool Started => DeviceThreadManage != null;
#else
/// <inheritdoc/>
[MinValue(1)]
public override int MaxConcurrentCount { get; set; }
/// <summary>
/// 设备数量
/// </summary>
public int? DeviceRuntimeCount { get; set; }
public bool Started { get; set; }
#endif
/// <summary>
/// 插件信息
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public PluginInfo? PluginInfo { get; set; }
/// <summary>
/// 是否采集
/// </summary>
public PluginTypeEnum? PluginType => PluginInfo?.PluginType;
/// <summary>
/// 是否采集
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public bool? IsCollect => PluginInfo == null ? null : PluginInfo?.PluginType == PluginTypeEnum.Collect;
public override string ToString()
{
if (ChannelType == ChannelTypeEnum.Other)
{
return Name;
}
return $"{Name}[{base.ToString()}]";
}
[AutoGenerateColumn(Ignore = true)]
public string LogPath => Name.GetChannelLogPath();
#if !Management
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public IDeviceThreadManage? DeviceThreadManage { get; internal set; }
[AutoGenerateColumn(Ignore = true)]
public string LogPath => Name.GetChannelLogPath();
public void Init()
{
// 通过插件名称获取插件信息
PluginInfo = GlobalData.PluginService.GetList().FirstOrDefault(A => A.FullName == PluginName);
PluginInfo = GlobalData.PluginService.GetPluginList().FirstOrDefault(A => A.FullName == PluginName);
GlobalData.IdChannels.TryRemove(Id, out _);
GlobalData.Channels.TryRemove(Name, out _);
@@ -135,14 +176,8 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
DeviceThreadManage = null;
GC.SuppressFinalize(this);
}
public override string ToString()
{
if (ChannelType == ChannelTypeEnum.Other)
{
return Name;
}
return $"{Name}[{base.ToString()}]";
}
public IChannel GetChannel(TouchSocketConfig config)
{
@@ -192,4 +227,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
return ichannel;
}
}
#endif
}

View File

@@ -23,12 +23,17 @@ namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 业务设备运行状态
/// </summary>
public class DeviceRuntime : Device, IDisposable
public class DeviceRuntime : Device
#if !Management
, IDisposable
#endif
{
protected volatile DeviceStatusEnum _deviceStatus = DeviceStatusEnum.Default;
private string? _lastErrorMessage;
private readonly object _lockObject = new object();
/// <summary>
/// 设备活跃时间
/// </summary>
@@ -67,11 +72,15 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)]
public string LogPath => Name.GetDeviceLogPath();
#if !Management
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
public DateTime DeviceStatusChangeTime = DateTime.UnixEpoch.ToLocalTime();
/// <summary>
/// 设备状态
/// </summary>
@@ -98,6 +107,58 @@ public class DeviceRuntime : Device, IDisposable
}
}
/// <summary>
/// 设备变量数量
/// </summary>
public int DeviceVariableCount { get => Driver == null ? VariableRuntimes?.Count ?? 0 : Driver.IdVariableRuntimes.Count; }
/// <summary>
/// 设备读取打包数量
/// </summary>
public int SourceVariableCount => VariableSourceReads?.Count ?? 0;
#else
/// <summary>
/// 设备状态
/// </summary>
public virtual DeviceStatusEnum DeviceStatus
{
get
{
if (!Pause)
return _deviceStatus;
else
return DeviceStatusEnum.Pause;
}
set
{
lock (_lockObject)
{
if (_deviceStatus != value)
{
_deviceStatus = value;
}
}
}
}
/// <summary>
/// 设备变量数量
/// </summary>
public int DeviceVariableCount { get; set; }
/// <summary>
/// 设备读取打包数量
/// </summary>
public int SourceVariableCount { get; set; }
#endif
/// <summary>
/// 暂停
/// </summary>
@@ -130,12 +191,17 @@ public class DeviceRuntime : Device, IDisposable
/// </summary>
public RedundantTypeEnum? RedundantType { get; set; } = null;
/// <summary>
/// 设备变量数量
/// </summary>
public int DeviceVariableCount { get => Driver == null ? VariableRuntimes?.Count ?? 0 : Driver.IdVariableRuntimes.Count; }
#region
/// <summary>
/// 特殊方法数量
/// </summary>
public int MethodVariableCount { get; set; }
#if !Management
/// <summary>
/// 设备变量
@@ -154,11 +220,6 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)]
internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; } = new(Environment.ProcessorCount, 1000);
/// <summary>
/// 特殊方法数量
/// </summary>
public int MethodVariableCount { get; set; }
/// <summary>
/// 特殊方法变量
/// </summary>
@@ -168,11 +229,6 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)]
public List<VariableMethod>? ReadVariableMethods { get; set; }
/// <summary>
/// 设备读取打包数量
/// </summary>
public int SourceVariableCount => VariableSourceReads?.Count ?? 0;
/// <summary>
/// 打包变量
/// </summary>
@@ -191,10 +247,11 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)]
public List<VariableScriptRead>? VariableScriptReads { get; set; }
public volatile bool CheckEnable;
private readonly object _lockObject = new object();
#endif
#endregion
#if !Management
/// <summary>
/// 传入设备的状态信息
@@ -218,6 +275,7 @@ public class DeviceRuntime : Device, IDisposable
LastErrorMessage = lastErrorMessage;
}
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
@@ -230,6 +288,7 @@ public class DeviceRuntime : Device, IDisposable
[AutoGenerateColumn(Ignore = true)]
public IRpcDriver? RpcDriver { get; set; }
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
@@ -263,4 +322,7 @@ public class DeviceRuntime : Device, IDisposable
GC.SuppressFinalize(this);
}
#endif
}

View File

@@ -17,17 +17,18 @@ namespace ThingsGateway.Gateway.Application;
/// </summary>
public class PluginInfo
{
#if !Management
/// <summary>
/// 插件文件名称.插件类型名称
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public List<PluginInfo>? Children { get; set; } = new();
#endif
/// <summary>
/// 插件文件名称.插件类型名称
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public string FullName => PluginServiceUtil.GetFullName(FileName, Name);
public string FullName => PluginInfoUtil.GetFullName(FileName, Name);
/// <summary>
/// 插件文件名称
@@ -70,8 +71,6 @@ public class PluginInfo
/// </summary>
[IgnoreExcel]
[SugarColumn(IsIgnore = true)]
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public string Directory { get; set; }
}

View File

@@ -8,9 +8,15 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Newtonsoft.Json.Linq;
using Riok.Mapperly.Abstractions;
#if !Management
using ThingsGateway.Gateway.Application.Extensions;
#endif
using ThingsGateway.NewLife.DictionaryExtensions;
using ThingsGateway.NewLife.Json.Extension;
@@ -19,38 +25,221 @@ namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 变量运行态
/// </summary>
public partial class VariableRuntime : Variable, IVariable, IDisposable
public partial class VariableRuntime : Variable
#if !Management
,
IVariable,
IDisposable
#endif
{
private DateTime? prepareAlarmEventTime;
private DateTime? prepareFinishEventTime;
private EventTypeEnum? eventType;
[AutoGenerateColumn(Visible = false)]
public bool ValueInited { get => _valueInited; set => _valueInited = value; }
#region
/// <summary>
/// 这个参数值由自动打包方法写入<see cref="IDevice.LoadSourceRead{T}(IEnumerable{IVariable}, int, string)"/>
/// </summary>
[AutoGenerateColumn(Visible = false)]
public int Index { get => index; set => index = value; }
/// <summary>
/// 变化时间
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public DateTime ChangeTime { get => changeTime; set => changeTime = value; }
/// <summary>
/// 采集时间
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public DateTime CollectTime { get => collectTime; set => collectTime = value; }
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
[AutoGenerateColumn(Visible = false, DefaultSort = false, Sortable = true)]
[IgnoreExcel]
public override int SortCode { get => sortCode; set => sortCode = value; }
/// <summary>
/// 上次值
/// </summary>
[AutoGenerateColumn(Visible = false, Order = 6)]
public object LastSetValue { get => lastSetValue; set => lastSetValue = value; }
/// <summary>
/// 原始值
/// </summary>
[AutoGenerateColumn(Visible = false, Order = 6)]
public object RawValue { get => rawValue; set => rawValue = value; }
#if !Management
/// <summary>
/// 所在采集设备
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public DeviceRuntime? DeviceRuntime { get => deviceRuntime; set => deviceRuntime = value; }
/// <summary>
/// VariableSource
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public IVariableSource? VariableSource { get => variableSource; set => variableSource = value; }
/// <summary>
/// VariableMethod
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public VariableMethod? VariableMethod { get => variableMethod; set => variableMethod = value; }
/// <summary>
/// 这个参数值由自动打包方法写入<see cref="IDevice.LoadSourceRead{T}(IEnumerable{IVariable}, int, string)"/>
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public IThingsGatewayBitConverter? ThingsGatewayBitConverter { get => thingsGatewayBitConverter; set => thingsGatewayBitConverter = value; }
#endif
/// <summary>
/// 是否在线
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public bool IsOnline
{
get
{
return _isOnline;
}
private set
{
if (IsOnline != value)
{
_isOnlineChanged = true;
}
else
{
_isOnlineChanged = false;
}
_isOnline = value;
}
}
#if !Management
/// <summary>
/// 设备名称
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 4)]
public string DeviceName => DeviceRuntime?.Name;
/// <summary>
/// <inheritdoc/>
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public string LastErrorMessage
{
get
{
if (_isOnline == false)
return _lastErrorMessage ?? VariableSource?.LastErrorMessage ?? VariableMethod?.LastErrorMessage;
else
return null;
}
}
/// <summary>
/// 实时值类型
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public string RuntimeType => Value?.GetType()?.ToString();
#else
/// <summary>
/// 设备名称
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 4)]
public string DeviceName { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public string LastErrorMessage { get; set; }
/// <summary>
/// 实时值类型
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public string RuntimeType { get; set; }
#endif
/// <summary>
/// 实时值
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public object Value { get => _value; set => _value = value; }
/// <summary>
/// 报警使能
/// </summary>
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool AlarmEnable
{
get
{
return AlarmPropertys != null && (AlarmPropertys.LAlarmEnable || AlarmPropertys.LLAlarmEnable || AlarmPropertys.HAlarmEnable || AlarmPropertys.HHAlarmEnable || AlarmPropertys.BoolOpenAlarmEnable || AlarmPropertys.BoolCloseAlarmEnable || AlarmPropertys.CustomAlarmEnable);
}
}
[IgnoreExcel]
[AutoGenerateColumn(Ignore = true)]
public AlarmRuntimePropertys? AlarmRuntimePropertys { get; set; }
#endregion
private AlarmTypeEnum? alarmType { get; set; }
private int index;
private int sortCode;
private DateTime changeTime = DateTime.UnixEpoch.ToLocalTime();
private DateTime alarmTime;
private DateTime eventTime;
private DateTime collectTime = DateTime.UnixEpoch.ToLocalTime();
private bool _isOnline;
#pragma warning disable CS0414
private bool _isOnlineChanged;
#pragma warning restore CS0414
private bool _valueInited;
private string alarmLimit;
private string alarmText;
private string alarmCode;
private string _lastErrorMessage;
private string recoveryCode;
private object _value;
private object lastSetValue;
private object rawValue;
#if !Management
#pragma warning disable CS0649
private string _lastErrorMessage;
#pragma warning restore CS0649
private DeviceRuntime? deviceRuntime;
private IVariableSource? variableSource;
private VariableMethod? variableMethod;
private IThingsGatewayBitConverter? thingsGatewayBitConverter;
/// <summary>
/// 设置变量值与时间/质量戳
/// </summary>
@@ -181,9 +370,9 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
public void Init(DeviceRuntime deviceRuntime)
{
GlobalData.AlarmEnableIdVariables.Remove(Id);
if (GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm))
if (!AlarmEnable && GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm))
{
oldAlarm.EventType = EventTypeEnum.Finish;
oldAlarm.EventType = EventTypeEnum.Restart;
oldAlarm.EventTime = DateTime.Now;
GlobalData.AlarmChange(this.AdaptAlarmVariable());
}
@@ -197,6 +386,7 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
GlobalData.IdVariables.TryAdd(Id, this);
if (AlarmEnable)
{
this.AlarmRuntimePropertys = new();
GlobalData.AlarmEnableIdVariables.TryAdd(Id, this);
}
}
@@ -210,7 +400,7 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
GlobalData.AlarmEnableIdVariables.Remove(Id);
if (GlobalData.RealAlarmIdVariables.TryRemove(Id, out var oldAlarm))
{
oldAlarm.EventType = EventTypeEnum.Finish;
oldAlarm.EventType = EventTypeEnum.Restart;
oldAlarm.EventTime = DateTime.Now;
GlobalData.AlarmChange(this.AdaptAlarmVariable());
}
@@ -232,4 +422,10 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
{
_lastErrorMessage = lastErrorMessage;
}
#endif
}

View File

@@ -1,225 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Riok.Mapperly.Abstractions;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 变量运行态
/// </summary>
public partial class VariableRuntime : Variable, IVariable, IDisposable
{
[AutoGenerateColumn(Visible = false)]
public bool ValueInited { get => _valueInited; set => _valueInited = value; }
#region
/// <summary>
/// 事件类型
/// </summary>
[AutoGenerateColumn(Visible = false)]
public EventTypeEnum? EventType { get => eventType; set => eventType = value; }
/// <summary>
/// 报警类型
/// </summary>
[AutoGenerateColumn(Visible = false)]
public AlarmTypeEnum? AlarmType { get => alarmType; set => alarmType = value; }
/// <summary>
/// 报警值
/// </summary>
[AutoGenerateColumn(Visible = false)]
public string AlarmCode { get => alarmCode; set => alarmCode = value; }
/// <summary>
/// 恢复值
/// </summary>
[AutoGenerateColumn(Visible = false)]
public string RecoveryCode { get => recoveryCode; set => recoveryCode = value; }
/// <summary>
/// 这个参数值由自动打包方法写入<see cref="IDevice.LoadSourceRead{T}(IEnumerable{IVariable}, int, string)"/>
/// </summary>
[AutoGenerateColumn(Visible = false)]
public int Index { get => index; set => index = value; }
/// <summary>
/// 事件时间
/// </summary>
[AutoGenerateColumn(Ignore = true)]
internal DateTime? PrepareAlarmEventTime { get => prepareAlarmEventTime; set => prepareAlarmEventTime = value; }
/// <summary>
/// 事件时间
/// </summary>
[AutoGenerateColumn(Ignore = true)]
internal DateTime? PrepareFinishEventTime { get => prepareFinishEventTime; set => prepareFinishEventTime = value; }
/// <summary>
/// 变化时间
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public DateTime ChangeTime { get => changeTime; set => changeTime = value; }
/// <summary>
/// 报警时间
/// </summary>
[AutoGenerateColumn(Visible = false)]
public DateTime AlarmTime { get => alarmTime; set => alarmTime = value; }
/// <summary>
/// 事件时间
/// </summary>
[AutoGenerateColumn(Visible = false)]
public DateTime EventTime { get => eventTime; set => eventTime = value; }
/// <summary>
/// 采集时间
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public DateTime CollectTime { get => collectTime; set => collectTime = value; }
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
[AutoGenerateColumn(Visible = false, DefaultSort = false, Sortable = true)]
[IgnoreExcel]
public override int SortCode { get => sortCode; set => sortCode = value; }
/// <summary>
/// 上次值
/// </summary>
[AutoGenerateColumn(Visible = false, Order = 6)]
public object LastSetValue { get => lastSetValue; set => lastSetValue = value; }
/// <summary>
/// 原始值
/// </summary>
[AutoGenerateColumn(Visible = false, Order = 6)]
public object RawValue { get => rawValue; set => rawValue = value; }
/// <summary>
/// 所在采集设备
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public DeviceRuntime? DeviceRuntime { get => deviceRuntime; set => deviceRuntime = value; }
/// <summary>
/// VariableSource
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public IVariableSource? VariableSource { get => variableSource; set => variableSource = value; }
/// <summary>
/// VariableMethod
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
public VariableMethod? VariableMethod { get => variableMethod; set => variableMethod = value; }
/// <summary>
/// 这个参数值由自动打包方法写入<see cref="IDevice.LoadSourceRead{T}(IEnumerable{IVariable}, int, string)"/>
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public IThingsGatewayBitConverter? ThingsGatewayBitConverter { get => thingsGatewayBitConverter; set => thingsGatewayBitConverter = value; }
/// <summary>
/// 设备名称
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 4)]
public string DeviceName => DeviceRuntime?.Name;
/// <summary>
/// 是否在线
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public bool IsOnline
{
get
{
return _isOnline;
}
private set
{
if (IsOnline != value)
{
_isOnlineChanged = true;
}
else
{
_isOnlineChanged = false;
}
_isOnline = value;
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
public string LastErrorMessage
{
get
{
if (_isOnline == false)
return _lastErrorMessage ?? VariableSource?.LastErrorMessage ?? VariableMethod?.LastErrorMessage;
else
return null;
}
}
/// <summary>
/// 实时值类型
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public string RuntimeType => Value?.GetType()?.ToString();
/// <summary>
/// 实时值
/// </summary>
[AutoGenerateColumn(Visible = true, Order = 6)]
public object Value { get => _value; set => _value = value; }
/// <summary>
/// 报警使能
/// </summary>
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public bool AlarmEnable
{
get
{
return LAlarmEnable || LLAlarmEnable || HAlarmEnable || HHAlarmEnable || BoolOpenAlarmEnable || BoolCloseAlarmEnable || CustomAlarmEnable;
}
}
/// <summary>
/// 报警限值
/// </summary>
[AutoGenerateColumn(Visible = false)]
public string AlarmLimit { get => alarmLimit; set => alarmLimit = value; }
/// <summary>
/// 报警文本
/// </summary>
[AutoGenerateColumn(Visible = false)]
public string AlarmText { get => alarmText; set => alarmText = value; }
#endregion
}

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 系统配置种子数据

View File

@@ -69,19 +69,19 @@ internal sealed class AlarmTask : IDisposable
return null; // 如果是则返回null
}
if (tag.BoolCloseAlarmEnable && !tag.Value.ToBoolean(true)) // 检查是否启用了关闭报警功能并且变量的布尔值为false
if (tag.AlarmPropertys.BoolCloseAlarmEnable && !tag.Value.ToBoolean(true)) // 检查是否启用了关闭报警功能并且变量的布尔值为false
{
limit = false.ToString(); // 将报警限制值设置为"false"
expressions = tag.BoolCloseRestrainExpressions!; // 获取关闭报警的约束表达式
text = tag.BoolCloseAlarmText!; // 获取关闭报警时的报警文本
expressions = tag.AlarmPropertys.BoolCloseRestrainExpressions!; // 获取关闭报警的约束表达式
text = tag.AlarmPropertys.BoolCloseAlarmText!; // 获取关闭报警时的报警文本
return AlarmTypeEnum.Close; // 返回关闭报警类型枚举
}
if (tag.BoolOpenAlarmEnable && tag.Value.ToBoolean(false)) // 检查是否启用了开启报警功能并且变量的布尔值为true
if (tag.AlarmPropertys.BoolOpenAlarmEnable && tag.Value.ToBoolean(false)) // 检查是否启用了开启报警功能并且变量的布尔值为true
{
limit = true.ToString(); // 将报警限制值设置为"true"
expressions = tag.BoolOpenRestrainExpressions!; // 获取开启报警的约束表达式
text = tag.BoolOpenAlarmText!; // 获取开启报警时的报警文本
expressions = tag.AlarmPropertys.BoolOpenRestrainExpressions!; // 获取开启报警的约束表达式
text = tag.AlarmPropertys.BoolOpenAlarmText!; // 获取开启报警时的报警文本
return AlarmTypeEnum.Open; // 返回开启报警类型枚举
}
@@ -107,18 +107,18 @@ internal sealed class AlarmTask : IDisposable
return null; // 如果是则返回null
}
if (tag.CustomAlarmEnable) // 检查是否启用了自定义报警功能
if (tag.AlarmPropertys.CustomAlarmEnable) // 检查是否启用了自定义报警功能
{
// 调用变量的CustomAlarmCode属性的GetExpressionsResult方法传入变量的值获取报警表达式的计算结果
var result = tag.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.DeviceRuntime?.Driver?.LogMessage);
var result = tag.AlarmPropertys.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.DeviceRuntime?.Driver?.LogMessage);
if (result is bool boolResult) // 检查计算结果是否为布尔类型
{
if (boolResult) // 如果计算结果为true
{
limit = tag.CustomAlarmCode; // 将报警限制值设置为自定义报警代码
expressions = tag.CustomRestrainExpressions!; // 获取自定义报警时的报警约束表达式
text = tag.CustomAlarmText!; // 获取自定义报警时的报警文本
limit = tag.AlarmPropertys.CustomAlarmCode; // 将报警限制值设置为自定义报警代码
expressions = tag.AlarmPropertys.CustomRestrainExpressions!; // 获取自定义报警时的报警约束表达式
text = tag.AlarmPropertys.CustomAlarmText!; // 获取自定义报警时的报警文本
return AlarmTypeEnum.Custom; // 返回自定义报警类型枚举
}
}
@@ -147,38 +147,38 @@ internal sealed class AlarmTask : IDisposable
}
// 检查是否启用了高高报警功能,并且变量的值大于高高报警的限制值
if (tag.HHAlarmEnable && tag.Value.ToDecimal() > tag.HHAlarmCode.ToDecimal())
if (tag.AlarmPropertys.HHAlarmEnable && tag.Value.ToDecimal() > tag.AlarmPropertys.HHAlarmCode)
{
limit = tag.HHAlarmCode.ToString()!; // 将报警限制值设置为高高报警的限制值
expressions = tag.HHRestrainExpressions!; // 获取高高报警的约束表达式
text = tag.HHAlarmText!; // 获取高高报警时的报警文本
limit = tag.AlarmPropertys.HHAlarmCode.ToString()!; // 将报警限制值设置为高高报警的限制值
expressions = tag.AlarmPropertys.HHRestrainExpressions!; // 获取高高报警的约束表达式
text = tag.AlarmPropertys.HHAlarmText!; // 获取高高报警时的报警文本
return AlarmTypeEnum.HH; // 返回高高报警类型枚举
}
// 检查是否启用了高报警功能,并且变量的值大于高报警的限制值
if (tag.HAlarmEnable && tag.Value.ToDecimal() > tag.HAlarmCode.ToDecimal())
if (tag.AlarmPropertys.HAlarmEnable && tag.Value.ToDecimal() > tag.AlarmPropertys.HAlarmCode)
{
limit = tag.HAlarmCode.ToString()!; // 将报警限制值设置为高报警的限制值
expressions = tag.HRestrainExpressions!; // 获取高报警的约束表达式
text = tag.HAlarmText!; // 获取高报警时的报警文本
limit = tag.AlarmPropertys.HAlarmCode.ToString()!; // 将报警限制值设置为高报警的限制值
expressions = tag.AlarmPropertys.HRestrainExpressions!; // 获取高报警的约束表达式
text = tag.AlarmPropertys.HAlarmText!; // 获取高报警时的报警文本
return AlarmTypeEnum.H; // 返回高报警类型枚举
}
// 检查是否启用了低低报警功能,并且变量的值小于低低报警的限制值
if (tag.LLAlarmEnable && tag.Value.ToDecimal() < tag.LLAlarmCode.ToDecimal())
if (tag.AlarmPropertys.LLAlarmEnable && tag.Value.ToDecimal() < tag.AlarmPropertys.LLAlarmCode)
{
limit = tag.LLAlarmCode.ToString()!; // 将报警限制值设置为低低报警的限制值
expressions = tag.LLRestrainExpressions!; // 获取低低报警的约束表达式
text = tag.LLAlarmText!; // 获取低低报警时的报警文本
limit = tag.AlarmPropertys.LLAlarmCode.ToString()!; // 将报警限制值设置为低低报警的限制值
expressions = tag.AlarmPropertys.LLRestrainExpressions!; // 获取低低报警的约束表达式
text = tag.AlarmPropertys.LLAlarmText!; // 获取低低报警时的报警文本
return AlarmTypeEnum.LL; // 返回低低报警类型枚举
}
// 检查是否启用了低报警功能,并且变量的值小于低报警的限制值
if (tag.LAlarmEnable && tag.Value.ToDecimal() < tag.LAlarmCode.ToDecimal())
if (tag.AlarmPropertys.LAlarmEnable && tag.Value.ToDecimal() < tag.AlarmPropertys.LAlarmCode)
{
limit = tag.LAlarmCode.ToString()!; // 将报警限制值设置为低报警的限制值
expressions = tag.LRestrainExpressions!; // 获取低报警的约束表达式
text = tag.LAlarmText!; // 获取低报警时的报警文本
limit = tag.AlarmPropertys.LAlarmCode.ToString()!; // 将报警限制值设置为低报警的限制值
expressions = tag.AlarmPropertys.LRestrainExpressions!; // 获取低报警的约束表达式
text = tag.AlarmPropertys.LAlarmText!; // 获取低报警时的报警文本
return AlarmTypeEnum.L; // 返回低报警类型枚举
}
@@ -195,7 +195,7 @@ internal sealed class AlarmTask : IDisposable
string ex; // 报警约束表达式
string text; // 报警文本
AlarmTypeEnum? alarmEnum; // 报警类型枚举
int delay = item.AlarmDelay; // 获取报警延迟时间
int delay = item.AlarmPropertys.AlarmDelay; // 获取报警延迟时间
// 检查变量的数据类型
if (item.Value?.GetType() == typeof(bool))
@@ -218,7 +218,7 @@ internal sealed class AlarmTask : IDisposable
if (alarmEnum == null)
{
// 如果仍未获取到报警类型,则触发需恢复报警事件(如果存在)
AlarmChange(item, null, text, EventTypeEnum.Finish, alarmEnum, delay);
AlarmChange(item, null, text, true, alarmEnum, delay);
}
else
{
@@ -233,14 +233,18 @@ internal sealed class AlarmTask : IDisposable
if (result)
{
// 如果表达式结果为true则触发报警事件
AlarmChange(item, limit, text, EventTypeEnum.Alarm, alarmEnum, delay);
AlarmChange(item, limit, text, false, alarmEnum, delay);
}
else
{
AlarmChange(item, limit, text, true, alarmEnum, delay);
}
}
}
else
{
// 如果不存在报警约束表达式,则直接触发报警事件
AlarmChange(item, limit, text, EventTypeEnum.Alarm, alarmEnum, delay);
AlarmChange(item, limit, text, false, alarmEnum, delay);
}
}
}
@@ -251,193 +255,233 @@ internal sealed class AlarmTask : IDisposable
/// <param name="item">要处理的变量</param>
/// <param name="limit">报警限制值</param>
/// <param name="text">报警文本</param>
/// <param name="eventEnum">报警事件类型枚举</param>
/// <param name="finish">是否恢复</param>
/// <param name="alarmEnum">报警类型枚举</param>
/// <param name="delay">报警延时</param>
private static void AlarmChange(VariableRuntime item, object limit, string text, EventTypeEnum eventEnum, AlarmTypeEnum? alarmEnum, int delay)
private static void AlarmChange(VariableRuntime item, object limit, string text, bool finish, AlarmTypeEnum? alarmEnum, int delay)
{
bool changed = false;
if (eventEnum == EventTypeEnum.Finish)
lock (item.AlarmRuntimePropertys.AlarmLockObject)
{
// 如果是需恢复报警事件
// 如果实时报警列表中不存在该变量,则直接返回
if (!GlobalData.RealAlarmIdVariables.ContainsKey(item.Id))
bool changed = false;
if (finish)
{
return;
}
}
else if (eventEnum == EventTypeEnum.Alarm)
{
// 如果是触发报警事件
// 在实时报警列表中查找该变量
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var variable))
{
// 如果变量已经处于相同的报警类型,则直接返回
if (item.AlarmType == alarmEnum)
// 如果是需恢复报警事件
// 如果实时报警列表中不存在该变量,则直接返回
if (!GlobalData.RealAlarmIdVariables.ContainsKey(item.Id))
{
return;
}
}
// 更新变量的报警信息和事件时间
if (eventEnum == EventTypeEnum.Alarm)
{
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
if (item.EventType != EventTypeEnum.Alarm && item.EventType != EventTypeEnum.PrepareAlarm)
{
item.EventType = EventTypeEnum.PrepareAlarm;//准备报警
item.PrepareAlarmEventTime = now;
}
else
{
if (item.EventType == EventTypeEnum.PrepareAlarm)
{
if ((now - item.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
//超过延时时间,触发报警
item.EventType = EventTypeEnum.Alarm;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else if (item.EventType == EventTypeEnum.Alarm && item.AlarmType != alarmEnum)
{
//报警类型改变,重新计时
if (item.PrepareAlarmEventTime == null)
item.PrepareAlarmEventTime = now;
if ((now - item.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
//超过延时时间,触发报警
item.EventType = EventTypeEnum.Alarm;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else
{
return;
}
}
}
else
{
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Confirm)
item.AlarmRuntimePropertys.AlarmConfirm = false;
// 如果是触发报警事件
item.EventType = eventEnum;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else if (eventEnum == EventTypeEnum.Finish)
{
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
if (item.EventType != EventTypeEnum.Finish && item.EventType != EventTypeEnum.PrepareFinish)
// 在实时报警列表中查找该变量
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var variable) && (variable.EventType == EventTypeEnum.Alarm || variable.EventType == EventTypeEnum.Confirm))
{
item.EventType = EventTypeEnum.PrepareFinish;//准备报警
item.PrepareFinishEventTime = now;
// 如果变量已经处于相同的报警类型,则直接返回
if (item.AlarmRuntimePropertys.AlarmType == alarmEnum)
return;
}
else
}
// 更新变量的报警信息和事件时间
if (!finish)
{
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
if (item.EventType == EventTypeEnum.PrepareFinish)
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Alarm && item.AlarmRuntimePropertys.EventType != EventTypeEnum.PrepareAlarm)
{
if ((now - item.PrepareFinishEventTime!.Value).TotalMilliseconds > delay)
item.AlarmRuntimePropertys.EventType = EventTypeEnum.PrepareAlarm;//准备报警
item.AlarmRuntimePropertys.PrepareAlarmEventTime = now;
}
else
{
if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.PrepareAlarm)
{
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
if ((now - item.AlarmRuntimePropertys.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
item.AlarmType = oldAlarm.AlarmType;
item.EventType = eventEnum;
item.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmCode = oldAlarm.AlarmCode;
item.RecoveryCode = item.Value.ToString();
item.AlarmText = oldAlarm.AlarmText;
item.EventTime = DateTime.Now;
item.PrepareFinishEventTime = null;
//超过延时时间,触发报警
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Alarm;
item.AlarmRuntimePropertys.AlarmTime = now;
item.AlarmRuntimePropertys.EventTime = now;
item.AlarmRuntimePropertys.AlarmType = alarmEnum;
item.AlarmRuntimePropertys.AlarmLimit = limit.ToString();
item.AlarmRuntimePropertys.AlarmCode = item.Value.ToString();
item.AlarmRuntimePropertys.RecoveryCode = string.Empty;
item.AlarmRuntimePropertys.AlarmText = text;
item.AlarmRuntimePropertys.PrepareAlarmEventTime = null;
changed = true;
}
}
else if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.Alarm && item.AlarmRuntimePropertys.AlarmType != alarmEnum)
{
//报警类型改变,重新计时
if (item.AlarmRuntimePropertys.PrepareAlarmEventTime == null)
item.AlarmRuntimePropertys.PrepareAlarmEventTime = now;
if ((now - item.AlarmRuntimePropertys.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
//超过延时时间,触发报警
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Alarm;
item.AlarmRuntimePropertys.AlarmTime = now;
item.AlarmRuntimePropertys.EventTime = now;
item.AlarmRuntimePropertys.AlarmType = alarmEnum;
item.AlarmRuntimePropertys.AlarmLimit = limit.ToString();
item.AlarmRuntimePropertys.AlarmCode = item.Value.ToString();
item.AlarmRuntimePropertys.RecoveryCode = string.Empty;
item.AlarmRuntimePropertys.AlarmText = text;
item.AlarmRuntimePropertys.PrepareAlarmEventTime = null;
changed = true;
}
}
else
{
return;
}
}
else
{
return;
}
}
else
{
// 如果是触发报警事件
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Alarm;
item.AlarmRuntimePropertys.AlarmTime = now;
item.AlarmRuntimePropertys.EventTime = now;
item.AlarmRuntimePropertys.AlarmType = alarmEnum;
item.AlarmRuntimePropertys.AlarmLimit = limit.ToString();
item.AlarmRuntimePropertys.AlarmCode = item.Value.ToString();
item.AlarmRuntimePropertys.RecoveryCode = string.Empty;
item.AlarmRuntimePropertys.AlarmText = text;
item.AlarmRuntimePropertys.PrepareAlarmEventTime = null;
changed = true;
}
}
else
{
// 如果是需恢复报警事件
// 获取旧的报警信息
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
item.AlarmType = oldAlarm.AlarmType;
item.EventType = eventEnum;
item.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmCode = oldAlarm.AlarmCode;
item.RecoveryCode = item.Value.ToString();
item.AlarmText = oldAlarm.AlarmText;
item.EventTime = DateTime.Now;
item.PrepareFinishEventTime = null;
changed = true;
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Finish && item.AlarmRuntimePropertys.EventType != EventTypeEnum.PrepareFinish)
{
item.AlarmRuntimePropertys.EventType = EventTypeEnum.PrepareFinish;
item.AlarmRuntimePropertys.PrepareFinishEventTime = now;
}
else
{
if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.PrepareFinish)
{
if ((now - item.AlarmRuntimePropertys.PrepareFinishEventTime!.Value).TotalMilliseconds > delay)
{
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
{
item.AlarmRuntimePropertys.AlarmType = oldAlarm.AlarmType;
item.AlarmRuntimePropertys.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmRuntimePropertys.AlarmCode = oldAlarm.AlarmCode;
item.AlarmRuntimePropertys.RecoveryCode = item.Value.ToString();
item.AlarmRuntimePropertys.AlarmText = oldAlarm.AlarmText;
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Finish)
{
item.AlarmRuntimePropertys.FinishTime = now;
item.AlarmRuntimePropertys.EventTime = now;
}
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Finish;
item.AlarmRuntimePropertys.PrepareFinishEventTime = null;
changed = true;
}
}
}
else
{
return;
}
}
}
else
{
// 如果是需恢复报警事件
// 获取旧的报警信息
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Finish && item.AlarmRuntimePropertys.EventType != EventTypeEnum.PrepareFinish)
{
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
{
item.AlarmRuntimePropertys.AlarmType = oldAlarm.AlarmType;
item.AlarmRuntimePropertys.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmRuntimePropertys.AlarmCode = oldAlarm.AlarmCode;
item.AlarmRuntimePropertys.RecoveryCode = item.Value.ToString();
item.AlarmRuntimePropertys.AlarmText = oldAlarm.AlarmText;
if (item.AlarmRuntimePropertys.EventType != EventTypeEnum.Finish)
{
item.AlarmRuntimePropertys.FinishTime = now;
item.AlarmRuntimePropertys.EventTime = now;
}
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Finish;
item.AlarmRuntimePropertys.PrepareFinishEventTime = null;
changed = true;
}
}
}
}
}
// 触发报警变化事件
if (changed)
{
if (item.EventType == EventTypeEnum.Alarm)
// 触发报警变化事件
if (changed)
{
// 如果是触发报警事件
//lock (GlobalData. RealAlarmVariables)
if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.Alarm)
{
// 从实时报警列表中移除旧的报警信息,并添加新的报警信息
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
// 如果是触发报警事件
//lock (GlobalData. RealAlarmVariables)
{
// 从实时报警列表中移除旧的报警信息,并添加新的报警信息
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
}
else if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.Finish)
{
// 如果是需恢复报警事件,则从实时报警列表中移除该变量
if (item.AlarmRuntimePropertys.AlarmConfirm)
{
GlobalData.RealAlarmIdVariables.TryRemove(item.Id, out _);
item.AlarmRuntimePropertys.EventType = EventTypeEnum.ConfirmAndFinish;
}
else
{
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
}
GlobalData.AlarmChange(item.AdaptAlarmVariable());
}
else if (item.EventType == EventTypeEnum.Finish)
{
// 如果是需恢复报警事件,则从实时报警列表中移除该变量
GlobalData.RealAlarmIdVariables.TryRemove(item.Id, out _);
//GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
GlobalData.AlarmChange(item.AdaptAlarmVariable());
}
}
public void ConfirmAlarm(long variableId)
{
// 如果是确认报警事件
if (GlobalData.AlarmEnableIdVariables.TryGetValue(variableId, out var variableRuntime))
if (GlobalData.AlarmEnableIdVariables.TryGetValue(variableId, out var item))
{
variableRuntime.EventType = EventTypeEnum.Confirm;
variableRuntime.EventTime = DateTime.Now;
GlobalData.RealAlarmIdVariables.AddOrUpdate(variableId, a => variableRuntime.AdaptAlarmVariable(), (a, b) => variableRuntime.AdaptAlarmVariable());
GlobalData.AlarmChange(variableRuntime.AdaptAlarmVariable());
lock (item.AlarmRuntimePropertys.AlarmLockObject)
{
item.AlarmRuntimePropertys.AlarmConfirm = true;
item.AlarmRuntimePropertys.ConfirmTime = DateTime.Now;
item.AlarmRuntimePropertys.EventTime = item.AlarmRuntimePropertys.ConfirmTime;
if (item.AlarmRuntimePropertys.EventType == EventTypeEnum.Finish)
{
item.AlarmRuntimePropertys.EventType = EventTypeEnum.ConfirmAndFinish;
GlobalData.RealAlarmIdVariables.TryRemove(variableId, out _);
}
else
{
item.AlarmRuntimePropertys.EventType = EventTypeEnum.Confirm;
GlobalData.RealAlarmIdVariables.AddOrUpdate(variableId, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
GlobalData.AlarmChange(item.AdaptAlarmVariable());
}
}
}

View File

@@ -8,11 +8,9 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
namespace ThingsGateway.Gateway.Application;
public interface IAlarmHostedService : IHostedService
public interface IAlarmHostedService
{
/// <summary>
/// 确认报警

View File

@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using TouchSocket.Dmtp.Rpc;
namespace ThingsGateway.Gateway.Application
{
public interface IRealAlarmService
{
[DmtpRpc]
Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync();
}
}

View File

@@ -8,13 +8,16 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Riok.Mapperly.Abstractions;
namespace ThingsGateway.Gateway.Application;
using TouchSocket.Dmtp;
namespace ThingsGateway.Upgrade;
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
public static partial class UpgradeMapper
/// <summary>
/// 设备采集报警后台服务
/// </summary>
internal sealed class RealAlarmService : IRealAlarmService
{
public static partial List<TcpSessionClientDto> AdaptListTcpSessionClientDto(this List<TcpDmtpSessionClient> src);
public Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync()
{
return GlobalData.GetCurrentUserRealAlarmVariablesAsync();
}
}

View File

@@ -19,13 +19,99 @@ namespace ThingsGateway.Gateway.Application;
public class ChannelRuntimeService : IChannelRuntimeService
{
private ILogger _logger;
public ChannelRuntimeService(ILogger<ChannelRuntimeService> logger)
private Microsoft.Extensions.Logging.ILogger _logger;
public ChannelRuntimeService(Microsoft.Extensions.Logging.ILogger<ChannelRuntimeService> logger)
{
_logger = logger;
}
private WaitLock WaitLock { get; set; } = new WaitLock(nameof(ChannelRuntimeService));
public async Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option)
{
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
var _channelItems = channels.GetQueryData(option, GatewayResourceUtil.BuildChannelSelectList);
return _channelItems;
}
public Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id)
{
GlobalData.IdChannels.TryGetValue(id, out var ChannelRuntime);
var data = ChannelRuntime?.DeviceThreadManage?.LogMessage?.LogLevel ?? TouchSocket.Core.LogLevel.Trace;
return Task.FromResult(data);
}
public async Task RestartChannelAsync(long channelId)
{
GlobalData.IdChannels.TryGetValue(channelId, out var channelRuntime);
await GlobalData.GetChannelThreadManage(channelRuntime).RestartChannelAsync(channelRuntime).ConfigureAwait(false);
}
public async Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel)
{
if (GlobalData.IdChannels.TryGetValue(id, out var ChannelRuntime))
{
if (ChannelRuntime.DeviceThreadManage != null)
{
await ChannelRuntime.DeviceThreadManage.SetLogAsync(logLevel).ConfigureAwait(false);
}
}
}
public async Task CopyChannelAsync(int CopyCount, string CopyChannelNamePrefix, int CopyChannelNameSuffixNumber, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long channelId, bool AutoRestartThread)
{
if (!GlobalData.IdChannels.TryGetValue(channelId, out var channelRuntime))
{
return;
}
Dictionary<Device, List<Variable>> deviceDict = new();
Channel Model = channelRuntime.AdaptChannel();
Model.Id = 0;
var Devices = channelRuntime.ReadDeviceRuntimes.ToDictionary(a => a.Value.AdaptDevice(), a => a.Value.ReadOnlyVariableRuntimes.Select(a => a.Value).AdaptListVariable());
List<Channel> channels = new();
Dictionary<Device, List<Variable>> devices = new();
for (int i = 0; i < CopyCount; i++)
{
Channel channel = Model.AdaptChannel();
channel.Id = CommonUtils.GetSingleId();
channel.Name = $"{CopyChannelNamePrefix}{CopyChannelNameSuffixNumber + i}";
int index = 0;
foreach (var item in Devices)
{
Device device = item.Key.AdaptDevice();
device.Id = CommonUtils.GetSingleId();
device.Name = $"{channel.Name}_{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + (index++)}";
device.ChannelId = channel.Id;
List<Variable> variables = new();
foreach (var variable in item.Value)
{
Variable v = variable.AdaptVariable();
v.Id = CommonUtils.GetSingleId();
v.DeviceId = device.Id;
variables.Add(v);
}
devices.Add(device, variables);
}
channels.Add(channel);
}
await GlobalData.ChannelRuntimeService.CopyAsync(channels, devices, AutoRestartThread, default).ConfigureAwait(false);
}
public async Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken)
{
try
@@ -122,7 +208,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
}
}
public async Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart)
public async Task<bool> BatchEditChannelAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart)
{
try
{
@@ -177,11 +263,101 @@ public class ChannelRuntimeService : IChannelRuntimeService
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>> ImportChannelAsync(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;
ChannelServiceHelpers.GetImportChannelData(data, out var upData, out var insertData);
var result = await GlobalData.ChannelService.ImportChannelAsync(upData, insertData).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(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
@@ -204,6 +380,31 @@ public class ChannelRuntimeService : IChannelRuntimeService
WaitLock.Release();
}
}
public async Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart)
{
try
{
await WaitLock.WaitAsync().ConfigureAwait(false);
var result = await GlobalData.ChannelService.ImportChannelAsync(upData, insertData).ConfigureAwait(false);
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false);
RuntimeServiceHelper.Init(newChannelRuntimes);
//根据条件重启通道线程
if (restart)
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
}
finally
{
WaitLock.Release();
}
}
public async Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart)
{
try

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,12 +255,12 @@ 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)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
var pluginInfo = GlobalData.PluginService.GetPluginList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = GlobalData.IdChannels.Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
}
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
@@ -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,23 +365,19 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
#region
/// <inheritdoc/>
[OperDesc("ImportChannel", isRecordPar: false, localizerType: typeof(Channel))]
public async Task<HashSet<long>> ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input)
public Task<HashSet<long>> ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input)
{
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;
}
}
var upData = channels.Where(a => a.IsUp).ToList();
var insertData = channels.Where(a => !a.IsUp).ToList();
ChannelServiceHelpers.GetImportChannelData(input, out var upData, out var insertData);
return ImportChannelAsync(upData, insertData);
}
public async Task<HashSet<long>> ImportChannelAsync(List<Channel> upData, List<Channel> insertData)
{
ManageHelper.CheckChannelCount(insertData.Count);
using var db = GetDB();
@@ -396,13 +392,23 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
await db.BulkUpdateAsync(upData, 10000).ConfigureAwait(false);
}
DeleteChannelFromCache();
return channels.Select(a => a.Id).ToHashSet();
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);
@@ -431,7 +437,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();
@@ -441,7 +447,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var channelProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name), a => (a, a.IsNullableType()));
string unportNull = App.CreateLocalizerByType(typeof(Channel))["ImportNullError"];
rows.ForEach(item =>
{
try
@@ -450,7 +456,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
if (channel == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, unportNull));
return;
}

View File

@@ -16,55 +16,26 @@ using ThingsGateway.Common.Extension;
namespace ThingsGateway.Gateway.Application;
public static class ChannelServiceHelpers
public static partial class ChannelServiceHelpers
{
public static USheetDatas ExportChannel(IEnumerable<Channel> channels)
public static void GetImportChannelData(Dictionary<string, ImportPreviewOutputBase> input, out List<Channel> upData, out List<Channel> insertData)
{
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)
List<Channel>? channels = new List<Channel>();
foreach (var item in input)
{
Dictionary<string, object> row = new();
foreach (var prop in propertyInfos)
if (item.Key == GatewayExportString.ChannelName)
{
var desc = type.GetPropertyDisplayName(prop.Name);
row.Add(desc ?? prop.Name, prop.GetValue(device)?.ToString());
var channelImports = ((ImportPreviewListOutput<Channel>)item.Value).Data;
channels = channelImports;
break;
}
yield return row;
}
upData = channels.Where(a => a.IsUp).ToList();
insertData = channels.Where(a => !a.IsUp).ToList();
}
internal static async IAsyncEnumerable<Dictionary<string, object>> ExportRows(IAsyncEnumerable<Channel>? data)
{
if (data == null)
@@ -108,13 +79,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

@@ -0,0 +1,64 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人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;
namespace ThingsGateway.Gateway.Application
{
public interface IChannelPageService
{
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);
/// <summary>
/// 保存通道
/// </summary>
/// <param name="input">通道对象</param>
/// <param name="type">保存类型</param>
/// <param name="restart">重启</param>
Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart);
/// <summary>
/// 批量修改
/// </summary>
/// <param name="models">列表</param>
/// <param name="oldModel">旧数据</param>
/// <param name="model">新数据</param>
/// <param name="restart">重启</param>
/// <returns></returns>
Task<bool> BatchEditChannelAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart);
/// <summary>
/// 删除通道
/// </summary>
Task<bool> DeleteChannelAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken);
Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart);
Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(USheetDatas input, bool restart);
Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(string filePath, bool restart);
Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IBrowserFile file, bool restart);
Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option);
}
}

View File

@@ -14,15 +14,8 @@ using Microsoft.AspNetCore.Components.Forms;
namespace ThingsGateway.Gateway.Application;
public interface IChannelRuntimeService
public interface IChannelRuntimeService : IChannelPageService
{
/// <summary>
/// 保存通道
/// </summary>
/// <param name="input">通道对象</param>
/// <param name="type">保存类型</param>
/// <param name="restart">重启</param>
Task<bool> SaveChannelAsync(Channel input, ItemChangedType type, bool restart);
/// <summary>
/// 保存通道
@@ -32,30 +25,21 @@ public interface IChannelRuntimeService
/// <param name="restart">重启</param>
Task<bool> BatchSaveChannelAsync(List<Channel> input, ItemChangedType type, bool restart);
/// <summary>
/// 批量修改
/// </summary>
/// <param name="models">列表</param>
/// <param name="oldModel">旧数据</param>
/// <param name="model">新数据</param>
/// <param name="restart">重启</param>
/// <returns></returns>
Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart);
/// <summary>
/// 删除通道
/// </summary>
Task<bool> DeleteChannelAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken);
/// <summary>
/// 导入通道数据
/// </summary>
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);
}

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,4 +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>> ImportChannelAsync(List<Channel> upData, List<Channel> insertData);
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string path);
}

View File

@@ -29,6 +29,46 @@ public class DeviceRuntimeService : IDeviceRuntimeService
private WaitLock WaitLock { get; set; } = new WaitLock(nameof(DeviceRuntimeService));
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, default).ConfigureAwait(false);
}
public async Task<bool> CopyAsync(Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken)
{
try
@@ -57,6 +97,10 @@ public class DeviceRuntimeService : IDeviceRuntimeService
}
}
public async Task<bool> BatchEditAsync(IEnumerable<Device> models, Device oldModel, Device model, bool restart)
{
try
@@ -122,7 +166,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);

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())
@@ -216,7 +216,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
}
if (exportFilter.PluginType != null)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
var pluginInfo = GlobalData.PluginService.GetPluginList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = (GlobalData.IdChannels).Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
}
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
@@ -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())
@@ -238,7 +238,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
}
if (exportFilter.PluginType != null)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
var pluginInfo = GlobalData.PluginService.GetPluginList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = (GlobalData.IdChannels).Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
}
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
@@ -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);
@@ -367,7 +367,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
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);
@@ -417,7 +417,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
ImportPreviewOutput<Device> deviceImportPreview = new();
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
var driverPluginNameDict = _pluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
var driverPluginNameDict = _pluginService.GetPluginList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
foreach (var sheetName in sheetNames)
{
@@ -439,8 +439,14 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
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
string ImportNullError = Localizer["ImportNullError"];
string RedundantDeviceError = Localizer["RedundantDeviceError"];
string ChannelError = Localizer["ChannelError"];
if (sheetName == ExportString.DeviceName)
string PluginNotNull = Localizer["PluginNotNull"];
string DeviceNotNull = Localizer["DeviceNotNull"];
if (sheetName == GatewayExportString.DeviceName)
{
// 初始化行数
int row = 1;
@@ -459,6 +465,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
var deviceProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name), a => (a, a.IsNullableType()));
// 遍历每一行数据
rows.ForEach(item =>
{
@@ -471,13 +479,13 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (device == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return;
}
// 转换冗余设备名称
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)
@@ -488,7 +496,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{
// 如果找不到对应的冗余设备,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["RedundantDeviceError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, RedundantDeviceError));
return;
}
}
@@ -498,7 +506,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (device.RedundantEnable)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["RedundantDeviceError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, RedundantDeviceError));
return;
}
}
@@ -512,7 +520,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{
// 如果找不到对应的通道信息,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ChannelError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ChannelError));
return;
}
}
@@ -520,7 +528,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{
// 如果未提供通道信息,则添加错误信息到导入预览结果并返回
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ChannelError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ChannelError));
return;
}
@@ -653,20 +661,20 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (propertys.Item1 == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["PluginNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, PluginNotNull));
continue;
}
// 获取设备名称
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, Localizer["DeviceNotNull"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull));
continue;
}
// 转化插件名称
var value = item[ExportString.DeviceName]?.ToString();
var value = item[GatewayExportString.DeviceName]?.ToString();
// 检查设备名称是否存在于设备导入预览数据中,如果不存在,则添加错误信息到导入预览结果并继续下一轮循环
var hasDevice = deviceImportPreview.Data.ContainsKey(value);
@@ -684,7 +692,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
if (pluginProp == null)
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, Localizer["ImportNullError"]));
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ImportNullError));
return;
}

View File

@@ -44,13 +44,13 @@ HashSet<string> pluginSheetNames,
data = new List<Device>();
var result = new Dictionary<string, object>();
result.Add(ExportString.DeviceName, GetDeviceSheets(data, deviceDicts, channelDicts, channelName));
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 = PluginServiceUtil.GetFileNameAndTypeName(plugin);
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin);
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
result.Add(filtResult.TypeName, pluginSheets);
}
@@ -69,13 +69,13 @@ 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)
{
var filtered = FilterPluginDevices(data2, plugin, channelDicts);
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin);
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
result.Add(filtResult.TypeName, pluginSheets);
}
@@ -209,7 +209,7 @@ string? channelName)
deviceDicts.TryGetValue(device.RedundantDeviceId ?? 0, out var redundantDevice);
channelDicts.TryGetValue(device.ChannelId, out var channel);
devExport.Add(ExportString.ChannelName, channel?.Name ?? channelName);
devExport.Add(GatewayExportString.ChannelName, channel?.Name ?? channelName);
foreach (var item in propertyInfos)
{
@@ -220,7 +220,7 @@ string? channelName)
}
//设备实体没有包含冗余设备名称,手动插入
devExport.Add(ExportString.RedundantDeviceName, redundantDevice?.Name);
devExport.Add(GatewayExportString.RedundantDeviceName, redundantDevice?.Name);
return devExport;
}
@@ -250,7 +250,7 @@ string? channelName)
if (propertys.Item2.Count > 0)
{
//没有包含设备名称,手动插入
driverInfo.Add(ExportString.DeviceName, device.Name);
driverInfo.Add(GatewayExportString.DeviceName, device.Name);
}
//根据插件的配置属性项生成列,从数据库中获取值或者获取属性默认值
foreach (var item in propertys.Item2)
@@ -288,10 +288,10 @@ string? channelName)
ImportPreviewOutput<Device> deviceImportPreview = new();
// 获取所有驱动程序,并将驱动程序的完整名称作为键构建字典
var driverPluginFullNameDict = GlobalData.PluginService.GetList().ToDictionary(a => a.FullName);
var driverPluginFullNameDict = GlobalData.PluginService.GetPluginList().ToDictionary(a => a.FullName);
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
var driverPluginNameDict = GlobalData.PluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
var driverPluginNameDict = GlobalData.PluginService.GetPluginList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
var sheetNames = uSheetDatas.sheets.Keys.ToList();

View File

@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application
{
public interface IDevicePageService
{
Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread);
}
}

View File

@@ -14,12 +14,12 @@ 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<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);

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>
/// 预览导入设备信息。

View File

@@ -21,7 +21,7 @@ internal sealed class BackendLogService : BaseService<BackendLog>, IBackendLogSe
/// <summary>
/// 最新十条
/// </summary>
public async Task<List<BackendLog>> GetNewLog()
public async Task<List<BackendLog>> GetNewBackendLogAsync()
{
using var db = GetDB();
var data = await db.Queryable<BackendLog>().OrderByDescending(a => a.LogTime).Take(10).ToListAsync().ConfigureAwait(false);
@@ -32,7 +32,7 @@ internal sealed class BackendLogService : BaseService<BackendLog>, IBackendLogSe
/// 表格查询
/// </summary>
/// <param name="option">查询条件</param>
public Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option)
public Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option)
{
return QueryAsync(option);
}
@@ -58,7 +58,7 @@ internal sealed class BackendLogService : BaseService<BackendLog>, IBackendLogSe
/// </summary>
/// <param name="day">天</param>
/// <returns>统计信息</returns>
public async Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day)
public async Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day)
{
using var db = GetDB();
//取最近七天

View File

@@ -10,13 +10,9 @@
using BootstrapBlazor.Components;
using TouchSocket.Dmtp.Rpc;
using TouchSocket.Rpc;
namespace ThingsGateway.Gateway.Application;
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
public interface IBackendLogService : IRpcServer
public interface IBackendLogService
{
/// <summary>
/// 删除 BackendLog 表中的所有记录
@@ -24,29 +20,25 @@ public interface IBackendLogService : IRpcServer
/// <remarks>
/// 调用此方法会删除 BackendLog 表中的所有记录。
/// </remarks>
[DmtpRpc]
Task DeleteBackendLogAsync();
/// <summary>
/// 获取最新的十条 BackendLog 记录
/// </summary>
/// <returns>最新的十条记录</returns>
[DmtpRpc]
Task<List<BackendLog>> GetNewLog();
Task<List<BackendLog>> GetNewBackendLogAsync();
/// <summary>
/// 分页查询 BackendLog 数据
/// </summary>
/// <param name="option">查询选项</param>
/// <returns>查询到的数据</returns>
[DmtpRpc]
Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option);
Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option);
/// <summary>
/// 获取最近一段时间内每天的后端日志统计信息
/// </summary>
/// <param name="day">要统计的天数</param>
/// <returns>按天统计的后端日志信息列表</returns>
[DmtpRpc]
Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day);
Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day);
}

View File

@@ -26,19 +26,19 @@ public interface IRpcLogService
/// 获取最新的十条 RpcLog 记录
/// </summary>
/// <returns>最新的十条记录</returns>
Task<List<RpcLog>> GetNewLog();
Task<List<RpcLog>> GetNewRpcLogAsync();
/// <summary>
/// 分页查询 RpcLog 数据
/// </summary>
/// <param name="option">查询选项</param>
/// <returns>查询到的数据</returns>
Task<QueryData<RpcLog>> PageAsync(QueryPageOptions option);
Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option);
/// <summary>
/// 按天统计 RpcLog 数据
/// </summary>
/// <param name="day">统计的天数</param>
/// <returns>按天统计的结果列表</returns>
Task<List<RpcLogDayStatisticsOutput>> StatisticsByDayAsync(int day);
Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day);
}

View File

@@ -21,7 +21,7 @@ internal sealed class RpcLogService : BaseService<RpcLog>, IRpcLogService
/// <summary>
/// 最新十条
/// </summary>
public async Task<List<RpcLog>> GetNewLog()
public async Task<List<RpcLog>> GetNewRpcLogAsync()
{
using var db = GetDB();
var data = await db.Queryable<RpcLog>().OrderByDescending(a => a.LogTime).Take(10).ToListAsync().ConfigureAwait(false);
@@ -32,7 +32,7 @@ internal sealed class RpcLogService : BaseService<RpcLog>, IRpcLogService
/// 表格查询
/// </summary>
/// <param name="option">查询条件</param>
public Task<QueryData<RpcLog>> PageAsync(QueryPageOptions option)
public Task<QueryData<RpcLog>> RpcLogPageAsync(QueryPageOptions option)
{
return QueryAsync(option);
}
@@ -51,7 +51,7 @@ internal sealed class RpcLogService : BaseService<RpcLog>, IRpcLogService
#endregion
public async Task<List<RpcLogDayStatisticsOutput>> StatisticsByDayAsync(int day)
public async Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day)
{
using var db = GetDB();
//取最近七天

View File

@@ -8,12 +8,11 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ThingsGateway.Gateway.Application;
public interface IGatewayMonitorHostedService : IHostedService
public interface IGatewayMonitorHostedService
{
public ILogger Logger { get; }
}

View File

@@ -0,0 +1,37 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
// ------------------------------------------------------------------------------
using ThingsGateway.Authentication;
namespace ThingsGateway.Gateway.Application;
internal sealed class AuthenticationService : IAuthenticationService
{
public Task<string> UUIDAsync() => Task.FromResult(ProAuthentication.UUID);
public Task<AuthorizeInfo> TryGetAuthorizeInfoAsync()
{
ProAuthentication.TryGetAuthorizeInfo(out var authorizeInfo);
return Task.FromResult(authorizeInfo);
}
public Task<AuthorizeInfo> TryAuthorizeAsync(string password)
{
ProAuthentication.TryAuthorize(password, out var authorizeInfo);
return Task.FromResult(authorizeInfo);
}
public Task UnAuthorizeAsync()
{
ProAuthentication.UnAuthorize();
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,21 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Authentication;
namespace ThingsGateway.Gateway.Application;
public interface IAuthenticationService
{
Task<string> UUIDAsync();
Task<AuthorizeInfo> TryAuthorizeAsync(string password);
Task<AuthorizeInfo> TryGetAuthorizeInfoAsync();
Task UnAuthorizeAsync();
}

View File

@@ -0,0 +1,26 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
// ------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
internal sealed class ChannelEnableService : IChannelEnableService
{
/// <summary>
/// 采集通道是否可用
/// </summary>
public Task<bool> StartCollectChannelEnableAsync() => Task.FromResult(GlobalData.StartCollectChannelEnable);
/// <summary>
/// 业务通道是否可用
/// </summary>
public Task<bool> StartBusinessChannelEnableAsync() => Task.FromResult(GlobalData.StartBusinessChannelEnable);
}

View File

@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using ThingsGateway.Extension.Generic;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Application;
public class GlobalDataService : IGlobalDataService
{
public async Task<IEnumerable<SelectedItem>> GetCurrentUserDeviceSelectedItemsAsync(string searchText, int startIndex, int count)
{
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
var items = devices.WhereIf(!searchText.IsNullOrWhiteSpace(), a => a.Name.Contains(searchText)).Skip(startIndex).Take(count)
.Select(a => new SelectedItem(a.Name, a.Name));
return items;
}
public Task<QueryData<SelectedItem>> GetCurrentUserDeviceVariableSelectedItemsAsync(string deviceText, string searchText, int startIndex, int count)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !searchText.IsNullOrWhiteSpace()
};
if ((!deviceText.IsNullOrWhiteSpace()) && GlobalData.ReadOnlyDevices.TryGetValue(deviceText, out var device))
{
var items = device.ReadOnlyVariableRuntimes.WhereIf(!searchText.IsNullOrWhiteSpace(), a => a.Value.Name.Contains(searchText)).Select(a => a.Value).Skip(startIndex).Take(count)
.Select(a => new SelectedItem(a.Name, a.Name)).ToList();
ret.TotalCount = items.Count;
ret.Items = items;
return Task.FromResult(ret);
}
else
{
ret.TotalCount = 0;
ret.Items = new List<SelectedItem>();
return Task.FromResult(ret);
}
}
}

Some files were not shown because too many files have changed in this diff Show More