Compare commits

...

21 Commits

Author SHA1 Message Date
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
Diego
34000d8d7d !68 10.10.7 2025-08-05 09:22:11 +00:00
2248356998 qq.com
e785f6660c 10.10.5 2025-08-01 21:53:47 +08:00
Diego
831c611797 10.10.4 2025-08-01 17:30:37 +08:00
Diego
453817ef86 添加IAsyncDisposable 2025-08-01 16:36:27 +08:00
2248356998 qq.com
8ce0b981c1 no message 2025-08-01 12:55:01 +08:00
2248356998 qq.com
4e5c51b54c 2025-08-01 12:47:21 +08:00
2248356998 qq.com
3cc9d31f28 修改可用内存策略 2025-08-01 12:43:39 +08:00
2248356998 qq.com
10391f869b 支持相对路径创建sqlite 2025-07-31 23:41:58 +08:00
389 changed files with 6740 additions and 7268 deletions

View File

@@ -38,6 +38,7 @@ public class VerificatInfo : PrimaryIdEntity
[AutoGenerateColumn(Filterable = true, Sortable = true)]
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[IgnoreExcel]
[System.ComponentModel.DataAnnotations.Key]
public override long Id { get; set; }
/// <summary>

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Admin.Application;
public class USheetDatas
{

View File

@@ -18,6 +18,7 @@ public class SessionOutput : PrimaryIdEntity
/// <summary>
/// 主键Id
/// </summary>
[System.ComponentModel.DataAnnotations.Key]
public override long Id { get; set; }
/// <summary>

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.Admin.Application;
public static class USheetDataHelpers
{
public static USheetDatas GetUSheetDatas(Dictionary<string, object> data)
{
var uSheetDatas = new USheetDatas();
foreach (var a in data)
{
var value = (a.Value as IEnumerable<Dictionary<string, object>>).ToList();
var uSheetData = new USheetData();
uSheetData.id = a.Key;
uSheetData.name = a.Key;
for (int row1 = 0; row1 < value.Count; row1++)
{
if (row1 == 0)
{
Dictionary<int, USheetCelldata> usheetColldata = new();
int col = 0;
foreach (var colData in value[row1])
{
usheetColldata.Add(col, new USheetCelldata() { v = colData.Key });
col++;
}
uSheetData.cellData.Add(row1, usheetColldata);
}
{
Dictionary<int, USheetCelldata> usheetColldata = new();
int col = 0;
foreach (var colData in value[row1])
{
usheetColldata.Add(col, new USheetCelldata() { v = colData.Value });
col++;
}
uSheetData.cellData.Add(row1 + 1, usheetColldata);
}
}
uSheetData.rowCount = uSheetData.cellData.Count + 100;
uSheetData.columnCount = uSheetData.cellData.FirstOrDefault().Value?.Count ?? 0;
uSheetDatas.sheets.Add(a.Key, uSheetData);
}
return uSheetDatas;
}
}

View File

@@ -1,7 +1,6 @@
@namespace ThingsGateway.Gateway.Razor
@namespace ThingsGateway.Admin.Razor
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Gateway.Application
<div class="h-600px">
<UniverSheet @ref="_sheetExcel" OnReadyAsync="OnReadyAsync"></UniverSheet>

View File

@@ -8,9 +8,10 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Admin.Application;
using ThingsGateway.NewLife.Json.Extension;
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Admin.Razor;
public partial class USheet
{

View File

@@ -6,6 +6,7 @@
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" />
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">

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

@@ -45,11 +45,11 @@ public class Startup : AppStartup
options.ServicesStopConcurrently = true;
});
//// 事件总线
//services.AddEventBus(options =>
//{
// 事件总线
services.AddEventBus(options =>
{
//});
});
// 任务调度
services.AddSchedule(options => options.AddPersistence<JobPersistence>());

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Common;
public class SmartTriggerScheduler
{

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Common;
public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string>
{

View File

@@ -27,11 +27,11 @@ public class WebsiteOptions : IConfigurableOptions
/// </summary>
public bool Demo { get; set; }
public bool WebPageEnable { get; set; } = true;
public int MaxBlazorConnections { get; set; } = 5;
public bool BlazorConnectionLimitEnable { get; set; } = false;
/// <summary>
/// 是否显示关于页面
/// </summary>

View File

@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
<PackageReference Include="BootstrapBlazor" Version="9.9.0" />
<PackageReference Include="BootstrapBlazor" Version="9.9.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -23,6 +23,7 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[IgnoreExcel]
[AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)]
[System.ComponentModel.DataAnnotations.Key]
public virtual long Id { get; set; }
}

View File

@@ -123,6 +123,15 @@ 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;
}

View File

@@ -46,7 +46,7 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
public async Task<bool> DeleteAsync(IEnumerable<T> models)
{
using var db = GetDB();
return await db.Deleteable<T>().In(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);
return await db.Deleteable<T>(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);
}
/// <inheritdoc/>
@@ -140,18 +140,22 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
return (await db.UpdateableT(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
}
}
/// <inheritdoc/>
public virtual async Task<bool> SaveAsync(List<T> model, ItemChangedType changedType)
{
return (await SaveReturnCountAsync(model, changedType).ConfigureAwait(false)) > 0;
}
/// <inheritdoc/>
public async Task<int> SaveReturnCountAsync(List<T> model, ItemChangedType changedType)
{
using var db = GetDB();
if (changedType == ItemChangedType.Add)
{
return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
return (await db.Insertable(model).ExecuteCommandAsync().ConfigureAwait(false));
}
else
{
return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
return (await db.Updateable(model).ExecuteCommandAsync().ConfigureAwait(false));
}
}
/// <summary>

View File

@@ -27,18 +27,27 @@ using System.Security.Claims;
using ThingsGateway.ConfigurableOptions;
using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Log;
using ThingsGateway.Reflection;
using ThingsGateway.Templates;
namespace ThingsGateway;
public static class WebEnableVariable
{
public static bool WebEnable => Environment.GetEnvironmentVariable(nameof(WebEnable)).ToBoolean(true);
}
/// <summary>
/// 全局应用类
/// </summary>
[SuppressSniffer]
public static class App
{
/// <summary>
/// 私有设置,避免重复解析
/// </summary>
@@ -157,7 +166,7 @@ public static class App
var httpContextAccessor = RootServices?.GetService<IHttpContextAccessor>();
try
{
return httpContextAccessor.HttpContext;
return httpContextAccessor?.HttpContext;
}
catch
{

View File

@@ -213,12 +213,18 @@ public static class AppServiceCollectionExtensions
// 缓存
if (cacheOptions.CacheType == CacheType.Memory)
{
services.AddSingleton<ICache, MemoryCache>(a => new()
services.AddSingleton<ICache>(a =>
{
Capacity = cacheOptions.MemoryCacheOptions.Capacity,
Expire = cacheOptions.MemoryCacheOptions.Expire,
Period = cacheOptions.MemoryCacheOptions.Period
});
Cache.Default = new MemoryCache()
{
Capacity = cacheOptions.MemoryCacheOptions.Capacity,
Expire = cacheOptions.MemoryCacheOptions.Expire,
Period = cacheOptions.MemoryCacheOptions.Period
};
return Cache.Default;
}
);
}
else if (cacheOptions.CacheType == CacheType.Redis)
{

View File

@@ -85,11 +85,14 @@ internal static class InternalApp
// 存储根服务(解决 Web 主机还未启动时在 HostedService 中使用 App.GetService 问题
services.AddHostedService<GenericHostLifetimeEventsHostedService>();
// 注册 Startup 过滤器
services.AddTransient<IStartupFilter, StartupFilter>();
if (WebEnableVariable.WebEnable == true)
{
// 注册 Startup 过滤器
services.AddTransient<IStartupFilter, StartupFilter>();
// 注册 HttpContextAccessor 服务
services.AddHttpContextAccessor();
// 注册 HttpContextAccessor 服务
services.AddHttpContextAccessor();
}
// 初始化应用服务
services.AddApp();

View File

@@ -20,6 +20,7 @@ namespace ThingsGateway;
/// </summary>
public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions>
{
/// <summary>
/// 是否启用规范化文档
/// </summary>

View File

@@ -0,0 +1,341 @@
// ------------------------------------------------------------------------
// 版权信息
// 版权归百小僧及百签科技(广东)有限公司所有。
// 所有权利保留。
// 官方网站https://baiqian.com
//
// 许可证信息
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// ------------------------------------------------------------------------
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ThingsGateway;
namespace System;
/// <summary>
/// <see cref="WebApplication"/> 方式配置选项
/// </summary>
[SuppressSniffer]
public sealed class MiniRunOptions : IRunOptions
{
/// <summary>
/// 内部构造函数
/// </summary>
internal MiniRunOptions()
{
}
/// <summary>
/// 默认配置
/// </summary>
public static MiniRunOptions Default { get; } = new MiniRunOptions();
/// <summary>
/// 默认配置(带启动参数)
/// </summary>
public static MiniRunOptions Main(string[] args)
{
return Default.WithArgs(args);
}
/// <summary>
/// 默认配置(静默启动)
/// </summary>
public static MiniRunOptions DefaultSilence { get; } = new MiniRunOptions().Silence();
/// <summary>
/// 默认配置(静默启动 + 启动参数)
/// </summary>
public static MiniRunOptions MainSilence(string[] args)
{
return DefaultSilence.WithArgs(args);
}
/// <summary>
/// 配置 <see cref="WebApplicationOptions"/>
/// </summary>
/// <param name="options"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureOptions(WebApplicationOptions options)
{
Options = options;
return this;
}
/// <summary>
/// 配置 <see cref="IWebHostBuilder"/>
/// </summary>
/// <param name="configureAction"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureBuilder(Action<IWebHostBuilder> configureAction)
{
ActionBuilder = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="IHostBuilder"/>
/// </summary>
/// <param name="configureAction"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureFirstActionBuilder(Action<IHostBuilder> configureAction)
{
FirstActionBuilder = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="IServiceCollection"/>
/// </summary>
/// <param name="configureAction"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureServices(Action<IServiceCollection> configureAction)
{
ActionServices = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="InjectOptions"/>
/// </summary>
/// <param name="configureAction"></param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureInject(Action<IWebHostBuilder, InjectOptions> configureAction)
{
ActionInject = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="WebApplication"/>
/// </summary>
/// <param name="configureAction">配置委托</param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions Configure(Action<IHost> configureAction)
{
ActionConfigure = configureAction;
return this;
}
/// <summary>
/// 配置 <see cref="ConfigurationManager"/>
/// </summary>
/// <param name="configureAction">配置委托</param>
/// <returns><see cref="MiniRunOptions"/></returns>
public MiniRunOptions ConfigureConfiguration(Action<IHostEnvironment, IConfiguration> configureAction)
{
ActionConfigurationManager = configureAction;
return this;
}
/// <summary>
/// 添加应用服务组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <returns></returns>
public MiniRunOptions AddComponent<TComponent>()
where TComponent : class, IServiceComponent, new()
{
ServiceComponents.Add(typeof(TComponent), null);
return this;
}
/// <summary>
/// 添加应用服务组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <typeparam name="TComponentOptions"></typeparam>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions AddComponent<TComponent, TComponentOptions>(TComponentOptions options)
where TComponent : class, IServiceComponent, new()
{
ServiceComponents.Add(typeof(TComponent), options);
return this;
}
/// <summary>
/// 添加应用服务组件
/// </summary>
/// <param name="componentType">组件类型</param>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions AddComponent(Type componentType, object options)
{
ServiceComponents.Add(componentType, options);
return this;
}
/// <summary>
/// 添加应用中间件组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <returns></returns>
public MiniRunOptions UseComponent<TComponent>()
where TComponent : class, IApplicationComponent, new()
{
ApplicationComponents.Add(typeof(TComponent), null);
return this;
}
/// <summary>
/// 添加应用中间件组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <typeparam name="TComponentOptions"></typeparam>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions UseComponent<TComponent, TComponentOptions>(TComponentOptions options)
where TComponent : class, IApplicationComponent, new()
{
ApplicationComponents.Add(typeof(TComponent), options);
return this;
}
/// <summary>
/// 添加应用中间件组件
/// </summary>
/// <param name="componentType">组件类型</param>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions UseComponent(Type componentType, object options)
{
ApplicationComponents.Add(componentType, options);
return this;
}
/// <summary>
/// 添加 IWebHostBuilder 组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <returns></returns>
public MiniRunOptions AddWebComponent<TComponent>()
where TComponent : class, IWebComponent, new()
{
WebComponents.Add(typeof(TComponent), null);
return this;
}
/// <summary>
/// 添加 IWebHostBuilder 组件
/// </summary>
/// <typeparam name="TComponent">组件类型</typeparam>
/// <typeparam name="TComponentOptions"></typeparam>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions AddWebComponent<TComponent, TComponentOptions>(TComponentOptions options)
where TComponent : class, IWebComponent, new()
{
WebComponents.Add(typeof(TComponent), options);
return this;
}
/// <summary>
/// 添加 IWebHostBuilder 组件
/// </summary>
/// <param name="componentType">组件类型</param>
/// <param name="options">组件参数</param>
/// <returns></returns>
public MiniRunOptions AddWebComponent(Type componentType, object options)
{
WebComponents.Add(componentType, options);
return this;
}
/// <summary>
/// 标识主机静默启动
/// </summary>
/// <remarks>不阻塞程序运行</remarks>
/// <param name="silence">静默启动</param>
/// <param name="logging">静默启动日志状态,默认 false</param>
/// <returns></returns>
public MiniRunOptions Silence(bool silence = true, bool logging = false)
{
IsSilence = silence;
SilenceLogging = logging;
return this;
}
/// <summary>
/// 设置进程启动参数
/// </summary>
/// <param name="args">启动参数</param>
/// <returns></returns>
public MiniRunOptions WithArgs(string[] args)
{
Args = args;
return this;
}
/// <summary>
/// <see cref="WebApplicationOptions"/>
/// </summary>
internal WebApplicationOptions Options { get; set; }
/// <summary>
/// 自定义 <see cref="IServiceCollection"/> 委托
/// </summary>
internal Action<IServiceCollection> ActionServices { get; set; }
/// <summary>
/// 自定义 <see cref="IWebHostBuilder"/> 委托
/// </summary>
internal Action<IHostBuilder> FirstActionBuilder { get; set; }
/// <summary>
/// 自定义 <see cref="IWebHostBuilder"/> 委托
/// </summary>
internal Action<IWebHostBuilder> ActionBuilder { get; set; }
/// <summary>
/// 自定义 <see cref="InjectOptions"/> 委托
/// </summary>
internal Action<IWebHostBuilder, InjectOptions> ActionInject { get; set; }
/// <summary>
/// 自定义 <see cref="IHost"/> 委托
/// </summary>
internal Action<IHost> ActionConfigure { get; set; }
/// <summary>
/// 自定义 <see cref="IConfiguration"/> 委托
/// </summary>
internal Action<IHostEnvironment, IConfiguration> ActionConfigurationManager { get; set; }
/// <summary>
/// 应用服务组件
/// </summary>
internal Dictionary<Type, object> ServiceComponents { get; set; } = new();
/// <summary>
/// IWebHostBuilder 组件
/// </summary>
internal Dictionary<Type, object> WebComponents { get; set; } = new();
/// <summary>
/// 应用中间件组件
/// </summary>
internal Dictionary<Type, object> ApplicationComponents { get; set; } = new();
/// <summary>
/// 静默启动
/// </summary>
/// <remarks>不阻塞程序运行</remarks>
internal bool IsSilence { get; private set; }
/// <summary>
/// 静默启动日志状态
/// </summary>
internal bool SilenceLogging { get; set; }
/// <summary>
/// 命令行参数
/// </summary>
internal string[] Args { get; set; }
}

View File

@@ -602,6 +602,33 @@ public static class Serve
return app;
}
/// <summary>
/// 启动 WebApplication 主机
/// </summary>
/// <remarks>未包含 Web 基础功能,需手动注册服务/中间件</remarks>
/// <param name="options">配置选项</param>
/// <param name="urls">默认 5000/5001 端口</param>
/// <param name="cancellationToken"></param>
/// <returns><see cref="IHost"/></returns>
public static async Task<IHost> RunAsync(MiniRunOptions options, string urls = default, CancellationToken cancellationToken = default)
{
// 构建 WebApplication 对象
BuildMiniApplication(options, urls, out var app);
// 是否静默启动
if (!options.IsSilence)
{
// 配置启动地址和端口
await app.RunAsync(cancellationToken).ConfigureAwait(false);
}
else
{
await app.StartAsync(cancellationToken).ConfigureAwait(false);
}
return app;
}
/// <summary>
/// 构建 WebApplication 对象
/// </summary>
@@ -616,8 +643,8 @@ public static class Serve
// 初始化 WebApplicationBuilder
var builder = (options.Options == null
? WebApplication.CreateBuilder(args)
: WebApplication.CreateBuilder(options.Options));
? WebApplication.CreateBuilder(args)
: WebApplication.CreateBuilder(options.Options));
// 调用自定义配置服务
options?.FirstActionBuilder?.Invoke(builder);
@@ -799,6 +826,132 @@ public static class Serve
App.AppStartups.Clear();
}
/// <summary>
/// 构建 IHost 对象
/// </summary>
/// <param name="options">配置选项</param>
/// <param name="urls">默认 5000/5001 端口</param>
/// <param name="app"><see cref="IHost"/></param>
public static void BuildMiniApplication(MiniRunOptions options, string urls, out IHost app)
{
// 获取命令行参数
var args = options.Args ?? Environment.GetCommandLineArgs().Skip(1).ToArray();
var builder = Host.CreateDefaultBuilder(args);
// 静默启动排除指定日志类名
if (options.IsSilence && !options.SilenceLogging)
{
builder = builder.ConfigureLogging(logging =>
{
logging.AddFilter((provider, category, logLevel) => !SilenceExcludesOfLogCategoryName.Any(u => category.StartsWith(u)));
});
}
// 配置 Web 主机
builder = builder.ConfigureWebHost(webHostBuilder =>
{
// 调用自定义配置服务
options?.FirstActionBuilder?.Invoke(builder);
// 注册 WebApplicationBuilder 组件
if (options.WebComponents.Count > 0)
{
foreach (var (componentType, opt) in options.WebComponents)
{
webHostBuilder.AddWebComponent(componentType, opt);
}
}
webHostBuilder.Configure((WebHostBuilderContext app, IApplicationBuilder applicationBuilder) =>
{
// 添加自定义配置
options.ActionConfigurationManager?.Invoke(app.HostingEnvironment, app.Configuration);
});
// 初始化框架
webHostBuilder.Inject(options.ActionInject);
// 配置服务
if (options.ServiceComponents.Count > 0)
{
webHostBuilder = webHostBuilder.ConfigureServices(services =>
{
// 注册应用服务组件
foreach (var (componentType, opt) in options.ServiceComponents)
{
services.AddComponent(componentType, opt);
}
});
}
// 配置启动地址和端口
var startUrls = !string.IsNullOrWhiteSpace(urls) ? urls : webHostBuilder.GetSetting(nameof(urls));
// 自定义启动端口
if (!string.IsNullOrWhiteSpace(startUrls))
{
webHostBuilder = webHostBuilder.UseUrls(startUrls);
}
// 调用自定义配置
options?.ActionBuilder?.Invoke(webHostBuilder);
// 配置中间件
if (options.ApplicationComponents.Count > 0)
{
webHostBuilder = webHostBuilder.Configure((context, app) =>
{
// 注册应用中间件组件
foreach (var (componentType, opt) in options.ApplicationComponents)
{
app.UseComponent(context.HostingEnvironment, componentType, opt);
}
});
}
});
builder = builder.ConfigureServices(services =>
{
// 调用自定义配置服务
options?.ActionServices?.Invoke(services);
});
// 构建主机
app = builder.Build();
InternalApp.RootServices ??= app.Services;
var applicationPartManager = app.Services.GetService<ApplicationPartManager>();
applicationPartManager?.ApplicationParts?.RemoveWhere(p => App.BakImageNames.Any(b => b == p.Name));
// 配置所有 Starup Configure
UseStartups(app.Services);
// 释放内存
App.AppStartups.Clear();
// 调用自定义配置
options?.ActionConfigure?.Invoke(app);
}
/// <summary>
/// 构建 IHost 对象
/// </summary>

View File

@@ -127,7 +127,8 @@ public sealed class DatabaseLogger : ILogger, IDisposable
// 设置日志消息模板
logMsg.Message = _options.MessageFormat != null
? _options.MessageFormat(logMsg)
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
: string.Empty;
//: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
// 空检查
if (logMsg.Message is null)

View File

@@ -683,13 +683,20 @@ public class MachineInfo : IExtend
if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
Memory = (UInt64)str.TrimEnd(" kB").ToLong();
ulong ma = 0;
if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
AvailableMemory = (UInt64)str.TrimEnd(" kB").ToLong();
else if (dic.TryGetValue("MemFree", out str) && !str.IsNullOrEmpty())
AvailableMemory =
(UInt64)(str.TrimEnd(" kB").ToLong() +
dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0 +
dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0);
{
ma = (UInt64)(str.TrimEnd(" kB").ToLong());
}
//低于3.14内核的版本用 free+cache
var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0);
var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0);
var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0);
var free = mf + mc + bf;
AvailableMemory = ma > free ? ma : free;
}
// A2/A4温度获取BuildrootCPU温度和主板温度

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

@@ -0,0 +1,6 @@
@namespace ThingsGateway.Razor
@if (show)
{
<Spinner class="ms-auto"></Spinner>
}

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Razor;
public partial class SpinnerComponent
{

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

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

@@ -111,7 +111,7 @@ namespace ThingsGateway.SqlSugar
/// <param name="context">SqlSugar提供者</param>
/// <param name="dataRecord">数据记录器</param>
/// <param name="fieldNames">字段名列表</param>
public IDataReaderEntityBuilder(SqlSugarProvider context, IDataRecord dataRecord, List<string> fieldNames)
public IDataReaderEntityBuilder(SqlSugarProvider context, IDataRecord dataRecord, IEnumerable<string> fieldNames)
{
this.Context = context;
this.DataRecord = dataRecord;

View File

@@ -679,7 +679,7 @@ namespace ThingsGateway.SqlSugar
IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
return cacheResult;
});
using (dr)
@@ -706,7 +706,7 @@ namespace ThingsGateway.SqlSugar
IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
return cacheResult;
});
if (cancellationToken.IsCancellationRequested) yield break;
@@ -743,7 +743,7 @@ namespace ThingsGateway.SqlSugar
IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
return cacheResult;
});
using (dr)
@@ -775,7 +775,7 @@ namespace ThingsGateway.SqlSugar
IDataReaderEntityBuilder<T> entytyList = this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey, () =>
{
var cacheResult = new IDataReaderEntityBuilder<T>(this.Context, dr,
columns.Select(it => it.Item1).ToList()).CreateBuilder(typeof(T));
columns.Select(it => it.Item1)).CreateBuilder(typeof(T));
return cacheResult;
});

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 => MemoryCache.Instance;
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]
{
@@ -86,7 +89,7 @@ namespace ThingsGateway.SqlSugar
public void Add(string key, V value, int cacheDurationInSeconds)
{
Check.ThrowNotSupportedException("ReflectionInoCache.Add(string key, V value, int cacheDurationInSeconds)");
this.InstanceCache.Add<V>(key, value, cacheDurationInSeconds);
}
public void Remove(string key)
@@ -107,9 +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());
return InstanceCache.GetOrAdd<V>(cacheKey, (a) =>
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

@@ -406,22 +406,24 @@ AND sql LIKE '%" + tableName + "%'");
public override bool CreateDatabase(string databaseName, string databaseDirectory = null)
{
var connString = this.Context.CurrentConnectionConfig.ConnectionString;
var path = Regex.Match(connString, @"[a-z,A-Z]\:\\.+\\").Value;
if (path.IsNullOrEmpty())
// 提取 Data Source=xxx不管是绝对还是相对路径
var match = Regex.Match(connString, @"(?i)Data\s+Source\s*=\s*(.+?)(;|$)");
if (match.Success)
{
path = Regex.Match(connString, @"\/.+\/").Value;
}
if (path.IsNullOrEmpty())
{
path = Regex.Match(connString, @"[a-z,A-Z]\:\\").Value;
}
if (!path.IsNullOrEmpty())
{
if (!FileHelper.IsExistDirectory(path))
var filePath = match.Groups[1].Value.Trim(); // => ./DB/data.sqlite
var folderPath = Path.GetDirectoryName(filePath); // => ./DB
if (!folderPath.IsNullOrEmpty())
{
FileHelper.CreateDirectory(path);
if (!FileHelper.IsExistDirectory(folderPath))
{
FileHelper.CreateDirectory(folderPath);
}
}
}
this.Context.Ado.Connection.Open();
this.Context.Ado.Connection.Close();
return true;

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,14 +1,15 @@
<Project>
<PropertyGroup>
<PluginVersion>10.10.1</PluginVersion>
<ProPluginVersion>10.10.1</ProPluginVersion>
<DefaultVersion>10.10.1</DefaultVersion>
<AuthenticationVersion>2.9.29</AuthenticationVersion>
<SourceGeneratorVersion>10.9.29</SourceGeneratorVersion>
<NET8Version>8.0.18</NET8Version>
<NET9Version>9.0.7</NET9Version>
<PluginVersion>10.10.12</PluginVersion>
<ProPluginVersion>10.10.12</ProPluginVersion>
<DefaultVersion>10.10.15</DefaultVersion>
<AuthenticationVersion>10.10.1</AuthenticationVersion>
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
<NET8Version>8.0.19</NET8Version>
<NET9Version>9.0.8</NET9Version>
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
<IsTrimmable>false</IsTrimmable>
</PropertyGroup>
<PropertyGroup>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

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

@@ -27,6 +27,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel
public OtherChannel(IChannelOptions channelOptions)
{
ChannelOptions = channelOptions;
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
@@ -39,7 +40,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
/// <inheritdoc/>
public IChannelOptions ChannelOptions { get; }
@@ -51,16 +52,16 @@ public class OtherChannel : SetupConfigObject, IClientChannel
public ConcurrentList<IDevice> Collects { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
public ChannelEventHandler Started { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
public ChannelEventHandler Starting { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
public ChannelEventHandler Stoped { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
public ChannelEventHandler Stoping { get; } = new();
/// <summary>
/// 等待池
/// </summary>

View File

@@ -24,6 +24,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
public SerialPortChannel(IChannelOptions channelOptions)
{
ChannelOptions = channelOptions;
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
@@ -36,7 +37,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
/// <inheritdoc/>
public IChannelOptions ChannelOptions { get; }
@@ -51,16 +52,16 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
public ChannelEventHandler Started { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
public ChannelEventHandler Starting { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
public ChannelEventHandler Stoped { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
public ChannelEventHandler Stoping { get; } = new();
/// <summary>
/// 等待池
/// </summary>

View File

@@ -23,7 +23,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
public TcpClientChannel(IChannelOptions channelOptions)
{
ChannelOptions = channelOptions;
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)

View File

@@ -134,6 +134,7 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
protected override void SafetyDispose(bool disposing)
{
m_transport?.SafeCancel();
m_transport?.SafeDispose();
base.SafetyDispose(disposing);
}
@@ -179,7 +180,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
/// <inheritdoc/>
public IChannelOptions ChannelOptions { get; }
@@ -191,15 +192,15 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
public bool Online => ServerState == ServerState.Running;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
public ChannelEventHandler Started { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
public ChannelEventHandler Starting { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
public ChannelEventHandler Stoped { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
public ChannelEventHandler Stoping { get; } = new();
/// <inheritdoc/>
public Task<Result> CloseAsync(string msg, CancellationToken token)
@@ -227,8 +228,8 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
data.ResetSign(MinSign, MaxSign);
return data;
}
public int MaxSign { get; set; }
public int MinSign { get; set; }
public int MaxSign { get; private set; } = 0;
public int MinSign { get; private set; } = ushort.MaxValue;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
{
MinSign = minSign;

View File

@@ -32,7 +32,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
/// <inheritdoc/>
public IChannelOptions ChannelOptions { get; internal set; }
@@ -47,15 +47,15 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
public ChannelEventHandler Started { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
public ChannelEventHandler Starting { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
public ChannelEventHandler Stoped { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
public ChannelEventHandler Stoping { get; } = new();
/// <summary>
/// 等待池

View File

@@ -25,6 +25,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel
public UdpSessionChannel(IChannelOptions channelOptions)
{
ChannelOptions = channelOptions;
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
@@ -37,7 +38,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
/// <inheritdoc/>
public IChannelOptions ChannelOptions { get; }
@@ -55,15 +56,15 @@ public class UdpSessionChannel : UdpSession, IClientChannel
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
public ChannelEventHandler Started { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
public ChannelEventHandler Starting { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
public ChannelEventHandler Stoped { get; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
public ChannelEventHandler Stoping { get; } = new();
/// <summary>
/// 等待池
@@ -204,6 +205,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel
/// <inheritdoc/>
protected override void SafetyDispose(bool disposing)
{
m_transport?.SafeCancel();
m_transport?.SafeDispose();
WaitHandlePool.SafeDispose();
base.SafetyDispose(disposing);

View File

@@ -24,7 +24,7 @@ namespace ThingsGateway.Foundation;
/// <summary>
/// 协议基类
/// </summary>
public abstract class DeviceBase : DisposableObject, IDevice
public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
/// <inheritdoc/>
public IChannel Channel { get; private set; }
@@ -553,11 +553,11 @@ public abstract class DeviceBase : DisposableObject, IDevice
WaitLock? waitLock = null;
try
{
await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null;
waitLock = GetWaitLock(clientChannel, dtuId);
await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
EndPoint? endPoint = GetUdpEndpoint(dtuId);
@@ -585,13 +585,6 @@ public abstract class DeviceBase : DisposableObject, IDevice
}
catch (Exception ex)
{
if (cancellationToken.IsCancellationRequested)
{
if (!this.DisposedValue)
{
await Task.Delay(timeout, Channel.ClosedToken).ConfigureAwait(false);
}
}
return new MessageBase(ex);
}
var result = waitData.Check();
@@ -1041,6 +1034,56 @@ public abstract class DeviceBase : DisposableObject, IDevice
base.Dispose(disposing);
}
/// <inheritdoc/>
protected override async Task DisposeAsync(bool disposing)
{
if (Channel != null)
{
Channel.Starting.Remove(ChannelStarting);
Channel.Stoped.Remove(ChannelStoped);
Channel.Started.Remove(ChannelStarted);
Channel.Stoping.Remove(ChannelStoping);
Channel.ChannelReceived.Remove(ChannelReceived);
if (Channel.Collects.Count == 1)
{
if (Channel is ITcpServiceChannel tcpServiceChannel)
{
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool.SafeDispose());
}
try
{
//只关闭,不释放
await Channel.CloseAsync().ConfigureAwait(false);
if (Channel is IClientChannel client)
{
client.WaitHandlePool.SafeDispose();
}
}
catch (Exception ex)
{
Logger?.LogWarning(ex);
}
}
else
{
if (Channel is ITcpServiceChannel tcpServiceChannel && this is IDtu dtu)
{
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
{
client.WaitHandlePool?.SafeDispose();
await client.CloseAsync().ConfigureAwait(false);
}
}
}
Channel.Collects.Remove(this);
}
_deviceLogger?.TryDispose();
base.Dispose(disposing);
}
/// <inheritdoc/>
public virtual Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config)
{
@@ -1058,4 +1101,5 @@ public abstract class DeviceBase : DisposableObject, IDevice
return a => { };
}
public abstract ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default);
}

View File

@@ -15,7 +15,7 @@ namespace ThingsGateway.Foundation;
/// <summary>
/// 协议设备接口
/// </summary>
public interface IDevice : IDisposable, IDisposableObject
public interface IDevice : IDisposable, IDisposableObject, IAsyncDisposable
{
#region

View File

@@ -8,6 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Buffers;
using System.Text;
namespace ThingsGateway.Foundation;
@@ -235,5 +236,28 @@ public static class ByteBlockExtension
}
#endregion AsSegment
#region ToString
public static string ToString<TByteBlock>(this TByteBlock byteBlock, long offset, long length) where TByteBlock : IBytesReader
{
return byteBlock.TotalSequence.Slice(offset, length).ToString(Encoding.UTF8);
}
public static string ToString(this ReadOnlySequence<byte> byteBlock, Encoding encoding)
{
# if NET6_0_OR_GREATER
return encoding.GetString(byteBlock);
#else
using ContiguousMemoryBuffer contiguousMemoryBuffer = new(byteBlock);
return contiguousMemoryBuffer.Memory.Span.ToString(encoding);
#endif
}
/// <inheritdoc/>
public static string ToString<TByteBlock>(this TByteBlock byteBlock, long offset) where TByteBlock : IBytesReader
{
return ToString(byteBlock, offset, byteBlock.BytesRead + byteBlock.BytesRemaining - offset);
}
#endregion ToString
}

View File

@@ -0,0 +1,73 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
// ------------------------------------------------------------------------------
namespace ThingsGateway;
public static class DisposableExtensions
{
#region IDisposable
/// <summary>
/// 安全性释放(不用判断对象是否为空)。不会抛出任何异常。
/// </summary>
/// <param name="dis"></param>
/// <returns>释放状态,当对象为<see langword="null"/>,或者已被释放时,均会返回<see cref="Result.Success"/>,只有实际在释放时遇到异常时,才显示其他状态。</returns>
public static async Task<Result> SafeDisposeAsync(this IAsyncDisposable dis)
{
if (dis == default)
{
return Result.Success;
}
try
{
await dis.DisposeAsync().ConfigureAwait(false);
return Result.Success;
}
catch (Exception ex)
{
return Result.FromException(ex);
}
}
#endregion IDisposable
#if NET8_0_OR_GREATER
/// <summary>
/// 安全地取消 <see cref="CancellationTokenSource"/>,并返回操作结果。
/// </summary>
/// <param name="tokenSource">要取消的 <see cref="CancellationTokenSource"/>。</param>
/// <returns>一个 <see cref="Result"/> 对象,表示操作的结果。</returns>
public static async Task<Result> SafeCancelAsync(this CancellationTokenSource tokenSource)
{
if (tokenSource is null)
{
return Result.Success;
}
try
{
await tokenSource.CancelAsync().ConfigureAwait(false);
return Result.Success;
}
catch (ObjectDisposedException)
{
return Result.Disposed;
}
catch (Exception ex)
{
return Result.FromException(ex);
}
}
#endif
}

View File

@@ -9,6 +9,7 @@
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
#pragma warning disable CA1851
public static class PackHelpers
{

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

@@ -15,6 +15,27 @@ namespace ThingsGateway.Foundation;
/// </summary>
public static class OperResultExtension
{
public static OperResult<object> GetOperResult(this IOperResult data)
{
OperResult<object> result = new(data);
var operResultType = typeof(IOperResult<>);
var interfaceType = data.GetType().GetInterfaces()
.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == operResultType);
if (interfaceType != null)
{
var contentProperty = interfaceType.GetProperty("Content");
if (contentProperty != null)
{
result.Content = contentProperty.GetValue(data);
}
}
return result;
}
/// <summary>
/// 转换对应类型
/// </summary>

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

@@ -0,0 +1,109 @@
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在XREF结尾的命名空间的代码归作者本人若汝棋茗所有
// 源代码使用协议遵循本仓库的开源协议及附加协议若本仓库没有设置则按MIT开源协议授权
// CSDN博客https://blog.csdn.net/qq_40374647
// 哔哩哔哩视频https://space.bilibili.com/94253567
// Gitee源代码仓库https://gitee.com/RRQM_Home
// Github源代码仓库https://github.com/RRQM
// API首页https://touchsocket.net/
// 交流QQ群234762506
// 感谢您的下载和使用
//------------------------------------------------------------------------------
using System.Runtime.CompilerServices;
namespace ThingsGateway.Foundation;
/// <summary>
/// 具有释放的对象。内部实现了<see cref="GC.SuppressFinalize(object)"/>,但不包括析构函数相关。
/// </summary>
public abstract partial class AsyncAndSyncDisposableObject :
IDisposableObject,
IAsyncDisposable
{
/// <summary>
/// 判断当前对象是否已经被释放。
/// 如果已经被释放,则抛出<see cref="ObjectDisposedException"/>异常。
/// </summary>
/// <exception cref="ObjectDisposedException">当对象已经被释放时抛出此异常</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
// 检查对象是否已经被释放
if (this.m_disposedValue)
{
// 如果对象已被释放抛出ObjectDisposedException异常
throw new ObjectDisposedException($"The object instance with type {this.GetType().FullName} has been released");
}
}
private int m_count = 0;
private int m_asyncCount = 0;
/// <summary>
/// 判断是否已释放。
/// </summary>
private volatile bool m_disposedValue;
/// <inheritdoc/>
public bool DisposedValue => this.m_disposedValue;
/// <summary>
/// 处置资源
/// </summary>
/// <param name="disposing">一个值,表示是否释放托管资源</param>
protected virtual void Dispose(bool disposing)
{
// 标记当前对象为已处置状态
this.m_disposedValue = true;
}
/// <summary>
/// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/>
/// </summary>
public void Dispose()
{
if (this.DisposedValue)
{
return;
}
if (Interlocked.Increment(ref this.m_count) == 1)
{
this.Dispose(disposing: true);
}
GC.SuppressFinalize(this);
}
/// <summary>
/// 处置资源
/// </summary>
/// <param name="disposing">一个值,表示是否释放托管资源</param>
protected virtual Task DisposeAsync(bool disposing)
{
// 标记当前对象为已处置状态
this.m_disposedValue = true;
return Task.CompletedTask;
}
/// <summary>
/// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/>
/// </summary>
public async ValueTask DisposeAsync()
{
if (this.DisposedValue)
{
return;
}
//if (Interlocked.Increment(ref this.m_count) == 1)
//{
// this.Dispose(disposing: true);
//}
if (Interlocked.Increment(ref this.m_asyncCount) == 1)
{
await this.DisposeAsync(disposing: true).ConfigureAwait(false);
}
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在XREF结尾的命名空间的代码归作者本人若汝棋茗所有
// 源代码使用协议遵循本仓库的开源协议及附加协议若本仓库没有设置则按MIT开源协议授权
// CSDN博客https://blog.csdn.net/qq_40374647
// 哔哩哔哩视频https://space.bilibili.com/94253567
// Gitee源代码仓库https://gitee.com/RRQM_Home
// Github源代码仓库https://github.com/RRQM
// API首页https://touchsocket.net/
// 交流QQ群234762506
// 感谢您的下载和使用
//------------------------------------------------------------------------------
using System.Runtime.CompilerServices;
namespace ThingsGateway.Foundation;
/// <summary>
/// 具有释放的对象。内部实现了<see cref="GC.SuppressFinalize(object)"/>,但不包括析构函数相关。
/// </summary>
public abstract partial class AsyncDisposableObject :
//IDisposableObject,
IAsyncDisposable
{
/// <summary>
/// 判断当前对象是否已经被释放。
/// 如果已经被释放,则抛出<see cref="ObjectDisposedException"/>异常。
/// </summary>
/// <exception cref="ObjectDisposedException">当对象已经被释放时抛出此异常</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
// 检查对象是否已经被释放
if (this.m_disposedValue)
{
// 如果对象已被释放抛出ObjectDisposedException异常
throw new ObjectDisposedException($"The object instance with type {this.GetType().FullName} has been released");
}
}
private int m_asyncCount = 0;
/// <summary>
/// 判断是否已释放。
/// </summary>
private volatile bool m_disposedValue;
/// <inheritdoc/>
public bool DisposedValue => this.m_disposedValue;
///// <summary>
///// 处置资源
///// </summary>
///// <param name="disposing">一个值,表示是否释放托管资源</param>
//protected virtual void Dispose(bool disposing)
//{
// // 标记当前对象为已处置状态
// this.m_disposedValue = true;
//}
///// <summary>
///// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/>
///// </summary>
//public void Dispose()
//{
// if (this.DisposedValue)
// {
// return;
// }
// if (Interlocked.Increment(ref this.m_count) == 1)
// {
// this.Dispose(disposing: true);
// }
// GC.SuppressFinalize(this);
//}
/// <summary>
/// 处置资源
/// </summary>
/// <param name="disposing">一个值,表示是否释放托管资源</param>
protected virtual Task DisposeAsync(bool disposing)
{
// 标记当前对象为已处置状态
this.m_disposedValue = true;
return Task.CompletedTask;
}
/// <summary>
/// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/>
/// </summary>
public async ValueTask DisposeAsync()
{
if (this.DisposedValue)
{
return;
}
//if (Interlocked.Increment(ref this.m_count) == 1)
//{
// this.Dispose(disposing: true);
//}
if (Interlocked.Increment(ref this.m_asyncCount) == 1)
{
await this.DisposeAsync(disposing: true).ConfigureAwait(false);
}
GC.SuppressFinalize(this);
}
}

View File

@@ -33,6 +33,8 @@ public class AsyncReadWriteLock
{
Interlocked.Increment(ref _readerCount);
// 第一个读者需要获取写入锁,防止写操作
await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
@@ -54,42 +56,47 @@ public class AsyncReadWriteLock
{
var cancellationTokenSource = _cancellationTokenSource;
_cancellationTokenSource = new();
await cancellationTokenSource.CancelAsync().ConfigureAwait(false); // 取消读取
await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取
cancellationTokenSource.SafeDispose();
}
return new Writer(this);
}
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();
}
else
{
// 读写占空比, 用于控制写操作与读操作的比率。该比率 n 次写入操作会执行一次读取操作。即使在应用程序执行大量的连续写入操作时,也必须确保足够的读取数据处理时间。相对于更加均衡的读写数据流而言,该特点使得外部写入可连续无顾忌操作
if (_writeReadRatio > 0)
lock (lockObject)
{
if (Interlocked.Read(ref _readerCount) > 0)
// 读写占空比, 用于控制写操作与读操作的比率。该比率 n 次写入操作会执行一次读取操作。即使在应用程序执行大量的连续写入操作时,也必须确保足够的读取数据处理时间。相对于更加均衡的读写数据流而言,该特点使得外部写入可连续无顾忌操作
if (_writeReadRatio > 0)
{
var count = Interlocked.Increment(ref _writeSinceLastReadCount);
if (count >= _writeReadRatio)
{
Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
_readerLock.Set();
//_readerLock.Set();
}
}
}
else
{
_readerLock.Set();
else
{
//_readerLock.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

@@ -35,6 +35,12 @@ 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
{

View File

@@ -16,20 +16,20 @@ namespace ThingsGateway.Gateway.Application;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class ThingsGatewayCacheConst
{
/// <summary>
/// 通道
/// </summary>
public const string Cache_Channel = $"{Cache_Prefix}Cache_Channel:List";
///// <summary>
///// 通道
///// </summary>
//public const string Cache_Channel = $"{Cache_Prefix}Cache_Channel:List";
/// <summary>
/// device
/// </summary>
public const string Cache_Device = $"{Cache_Prefix}Cache_Device:List";
///// <summary>
///// device
///// </summary>
//public const string Cache_Device = $"{Cache_Prefix}Cache_Device:List";
/// <summary>
/// variable
/// </summary>
public const string Cache_Variable = $"{Cache_Prefix}Cache_Variable:IdNameList";
///// <summary>
///// variable
///// </summary>
//public const string Cache_Variable = $"{Cache_Prefix}Cache_Variable:IdNameList";
/// <summary>
/// 前缀

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,13 +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.Sockets;
using TouchSocket.Rpc;
namespace ThingsGateway.Gateway.Application;
@@ -34,7 +31,9 @@ namespace ThingsGateway.Gateway.Application;
[RequestAudit]
[ApiController]
[Authorize(AuthenticationSchemes = "Bearer")]
public class ControlController : ControllerBase
[TouchSocket.WebApi.Router("/miniapi/[api]/[action]")]
[TouchSocket.WebApi.EnableCors("cors")]
public class ControlController : ControllerBase, IRpcServer
{
/// <summary>
@@ -43,6 +42,7 @@ public class ControlController : ControllerBase
/// <returns></returns>
[HttpPost("removeAllCache")]
[DisplayName("清空全部缓存")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public void RemoveAllCache()
{
App.CacheService.Clear();
@@ -54,6 +54,7 @@ public class ControlController : ControllerBase
/// <returns></returns>
[HttpPost("removeCache")]
[DisplayName("删除通道/设备缓存")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public void RemoveCache()
{
App.GetService<IDeviceService>().DeleteDeviceFromCache();
@@ -66,6 +67,7 @@ public class ControlController : ControllerBase
/// <returns></returns>
[HttpPost("pauseBusinessThread")]
[DisplayName("控制设备线程启停")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task PauseDeviceThreadAsync(long id, bool pause)
{
if (GlobalData.IdDevices.TryGetValue(id, out var device))
@@ -85,6 +87,7 @@ public class ControlController : ControllerBase
/// <returns></returns>
[HttpPost("restartScopeThread")]
[DisplayName("重启当前机构线程")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task RestartScopeThread()
{
var data = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
@@ -97,6 +100,7 @@ public class ControlController : ControllerBase
/// <returns></returns>
[HttpPost("restartAllThread")]
[DisplayName("重启全部线程")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task RestartAllThread()
{
await GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.IdChannels.Values).ConfigureAwait(false);
@@ -108,6 +112,7 @@ public class ControlController : ControllerBase
/// <returns></returns>
[HttpPost("restartThread")]
[DisplayName("重启设备线程")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task RestartDeviceThreadAsync(long deviceId)
{
if (GlobalData.IdDevices.TryGetValue(deviceId, out var deviceRuntime))
@@ -126,7 +131,8 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("writeVariables")]
[DisplayName("写入变量")]
public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody][TouchSocket.WebApi.FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas)
{
foreach (var deviceData in deviceDatas)
{
@@ -145,9 +151,10 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("batchSaveChannel")]
[DisplayName("保存通道")]
public Task<bool> BatchSaveChannelAsync([FromBody] List<ChannelInput> channels, ItemChangedType type, bool restart = true)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> BatchSaveChannelAsync([FromBody][TouchSocket.WebApi.FromBody] List<Channel> channels, ItemChangedType type, bool restart = true)
{
return GlobalData.ChannelRuntimeService.BatchSaveChannelAsync(channels.AdaptListChannel(), type, restart);
return GlobalData.ChannelRuntimeService.BatchSaveChannelAsync(channels, type, restart);
}
/// <summary>
@@ -155,9 +162,10 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("batchSaveDevice")]
[DisplayName("保存设备")]
public Task<bool> BatchSaveDeviceAsync([FromBody] List<DeviceInput> devices, ItemChangedType type, bool restart = true)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> BatchSaveDeviceAsync([FromBody][TouchSocket.WebApi.FromBody] List<Device> devices, ItemChangedType type, bool restart = true)
{
return GlobalData.DeviceRuntimeService.BatchSaveDeviceAsync(devices.AdaptListDevice(), type, restart);
return GlobalData.DeviceRuntimeService.BatchSaveDeviceAsync(devices, type, restart);
}
/// <summary>
@@ -165,9 +173,10 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("batchSaveVariable")]
[DisplayName("保存变量")]
public Task<bool> BatchSaveVariableAsync([FromBody] List<VariableInput> variables, ItemChangedType type, bool restart = true)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> BatchSaveVariableAsync([FromBody][TouchSocket.WebApi.FromBody] List<Variable> variables, ItemChangedType type, bool restart = true)
{
return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables.AdaptListVariable(), type, restart, default);
return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables, type, restart, default);
}
/// <summary>
@@ -175,7 +184,8 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("deleteChannel")]
[DisplayName("删除通道")]
public Task<bool> DeleteChannelAsync([FromBody] List<long> ids, bool restart = true)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> DeleteChannelAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true)
{
if (ids == null || ids.Count == 0) ids = GlobalData.IdChannels.Keys.ToList();
return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart, default);
@@ -186,7 +196,8 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("deleteDevice")]
[DisplayName("删除设备")]
public Task<bool> DeleteDeviceAsync([FromBody] List<long> ids, bool restart = true)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> DeleteDeviceAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true)
{
if (ids == null || ids.Count == 0) ids = GlobalData.IdDevices.Keys.ToList();
return GlobalData.DeviceRuntimeService.DeleteDeviceAsync(ids, restart, default);
@@ -197,7 +208,8 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("deleteVariable")]
[DisplayName("删除变量")]
public Task<bool> DeleteVariableAsync([FromBody] List<long> ids, bool restart = true)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> DeleteVariableAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true)
{
if (ids == null || ids.Count == 0) ids = GlobalData.IdVariables.Keys.ToList();
return GlobalData.VariableRuntimeService.DeleteVariableAsync(ids, restart, default);
@@ -208,6 +220,7 @@ public class ControlController : ControllerBase
/// </summary>
[HttpPost("insertTestData")]
[DisplayName("增加测试数据")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart = true)
{
return GlobalData.VariableRuntimeService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart, default);
@@ -220,6 +233,7 @@ public class ControlController : ControllerBase
[HttpPost("checkRealAlarm")]
[RequestAudit]
[DisplayName("确认实时报警")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task CheckRealAlarm(long variableId)
{
if (GlobalData.ReadOnlyRealAlarmIdVariables.TryGetValue(variableId, out var variable))
@@ -229,552 +243,3 @@ public class ControlController : ControllerBase
}
}
}
public class ChannelInput
{
/// <summary>
/// 主键Id
/// </summary>
public virtual long Id { get; set; }
/// <summary>
/// 通道名称
/// </summary>
[Required]
public virtual string Name { get; set; }
/// <inheritdoc/>
public virtual ChannelTypeEnum ChannelType { get; set; }
/// <summary>
/// 插件名称
/// </summary>
[Required]
public virtual string PluginName { get; set; }
/// <summary>
/// 使能
/// </summary>
public virtual bool Enable { get; set; } = true;
/// <summary>
/// LogLevel
/// </summary>
public LogLevel LogLevel { get; set; } = LogLevel.Info;
/// <summary>
/// 远程地址,可由<see cref="IPHost"/> 与 <see cref="string"/> 相互转化
/// </summary>
[UriValidation]
public virtual string RemoteUrl { get; set; } = "127.0.0.1:502";
/// <summary>
/// 本地地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
/// </summary>
[UriValidation]
public virtual string BindUrl { get; set; }
/// <summary>
/// COM
/// </summary>
public virtual string PortName { get; set; } = "COM1";
/// <summary>
/// 波特率
/// </summary>
public virtual int BaudRate { get; set; } = 9600;
/// <summary>
/// 数据位
/// </summary>
public virtual int DataBits { get; set; } = 8;
/// <summary>
/// 校验位
/// </summary>
public virtual Parity Parity { get; set; }
/// <summary>
/// 停止位
/// </summary>
public virtual StopBits StopBits { get; set; } = StopBits.One;
/// <summary>
/// DtrEnable
/// </summary>
public virtual bool DtrEnable { get; set; }
/// <summary>
/// RtsEnable
/// </summary>
public virtual bool RtsEnable { get; set; }
/// <summary>
/// StreamAsync
/// </summary>
public virtual bool StreamAsync { get; set; } = false;
/// <summary>
/// 缓存超时
/// </summary>
[MinValue(100)]
public virtual int CacheTimeout { get; set; } = 500;
/// <summary>
/// 连接超时
/// </summary>
[MinValue(100)]
public virtual ushort ConnectTimeout { get; set; } = 3000;
/// <summary>
/// 最大并发数
/// </summary>
[MinValue(1)]
public virtual int MaxConcurrentCount { get; set; } = 1;
public virtual int MaxClientCount { get; set; } = 10000;
public virtual int CheckClearTime { get; set; } = 120000;
public virtual string Heartbeat { get; set; } = "Heartbeat";
#region dtu终端
public virtual int HeartbeatTime { get; set; } = 60000;
public virtual string DtuId { get; set; }
#endregion
public virtual DtuSeviceType DtuSeviceType { get; set; }
}
public class DeviceInput : IValidatableObject
{
public long Id { get; set; }
/// <summary>
/// 名称
/// </summary>
[Required]
[RegularExpression(@"^[^.]*$", ErrorMessage = "The field {0} cannot contain a dot ('.')")]
public virtual string Name { get; set; }
/// <summary>
/// 描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 通道
/// </summary>
[MinValue(1)]
[Required]
public virtual long ChannelId { get; set; }
/// <summary>
/// 默认执行间隔支持corn表达式
/// </summary>
public virtual string IntervalTime { get; set; } = "1000";
/// <summary>
/// 设备使能
/// </summary>
public virtual bool Enable { get; set; } = true;
/// <summary>
/// LogLevel
/// </summary>
public virtual TouchSocket.Core.LogLevel LogLevel { get; set; } = TouchSocket.Core.LogLevel.Info;
/// <summary>
/// 设备属性Json
/// </summary>
public Dictionary<string, string>? DevicePropertys { get; set; } = new();
#region
/// <summary>
/// 启用冗余
/// </summary>
public bool RedundantEnable { get; set; }
/// <summary>
/// 冗余设备Id,只能选择相同驱动
/// </summary>
public long? RedundantDeviceId { get; set; }
/// <summary>
/// 冗余模式
/// </summary>
public virtual RedundantSwitchTypeEnum RedundantSwitchType { get; set; }
/// <summary>
/// 冗余扫描间隔
/// </summary>
[MinValue(30000)]
public virtual int RedundantScanIntervalTime { get; set; } = 30000;
/// <summary>
/// 冗余切换判断脚本返回true则切换冗余设备
/// </summary>
public virtual string RedundantScript { get; set; }
#endregion
#region
/// <summary>
/// 自定义
/// </summary>
public string? Remark1 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark2 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark3 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark4 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark5 { get; set; }
#endregion
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (RedundantEnable && RedundantDeviceId == null)
{
yield return new ValidationResult("When enable redundancy, you must select a redundant device.", new[] { nameof(RedundantEnable), nameof(RedundantDeviceId) });
}
}
}
public class VariableInput : IValidatableObject
{
public virtual long Id { get; set; }
/// <summary>
/// 设备
/// </summary>
[Required]
public virtual long DeviceId { get; set; }
/// <summary>
/// 变量名称
/// </summary>
[Required]
public virtual string Name { get; set; }
/// <summary>
/// 描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 单位
/// </summary>
public virtual string? Unit { get; set; }
/// <summary>
/// 间隔时间
/// </summary>
public virtual string? IntervalTime { get; set; }
/// <summary>
/// 变量地址,可能带有额外的信息,比如<see cref="DataFormatEnum"/> ,以;分割
/// </summary>
public string? RegisterAddress { get; set; }
/// <summary>
/// 数组长度
/// </summary>
public int? ArrayLength { get; set; }
/// <summary>
/// 其他方法若不为空此时RegisterAddress为方法参数
/// </summary>
public string? OtherMethod { get; set; }
/// <summary>
/// 使能
/// </summary>
public virtual bool Enable { get; set; } = true;
/// <summary>
/// 读写权限
/// </summary>
public virtual ProtectTypeEnum ProtectType { get; set; } = ProtectTypeEnum.ReadWrite;
/// <summary>
/// 数据类型
/// </summary>
public virtual DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16;
/// <summary>
/// 读取表达式
/// </summary>
public virtual string? ReadExpressions { get; set; }
/// <summary>
/// 写入表达式
/// </summary>
public virtual string? WriteExpressions { get; set; }
/// <summary>
/// 是否允许远程Rpc写入
/// </summary>
public virtual bool RpcWriteEnable { get; set; } = true;
/// <summary>
/// 初始值
/// </summary>
public object? InitValue
{
get
{
return _value;
}
set
{
if (value != null)
_value = value?.ToString()?.GetJTokenFromString();
else
_value = null;
}
}
private object? _value;
/// <summary>
/// 保存初始值
/// </summary>
public virtual bool SaveValue { get; set; } = false;
/// <summary>
/// 变量额外属性Json
/// </summary>
public Dictionary<long, Dictionary<string, string>>? VariablePropertys { get; set; }
#region
/// <summary>
/// 报警延时
/// </summary>
public int AlarmDelay { get; set; }
/// <summary>
/// 布尔开报警使能
/// </summary>
public bool BoolOpenAlarmEnable { get; set; }
/// <summary>
/// 布尔开报警约束
/// </summary>
public string? BoolOpenRestrainExpressions { get; set; }
/// <summary>
/// 布尔开报警文本
/// </summary>
public string? BoolOpenAlarmText { get; set; }
/// <summary>
/// 布尔关报警使能
/// </summary>
public bool BoolCloseAlarmEnable { get; set; }
/// <summary>
/// 布尔关报警约束
/// </summary>
public string? BoolCloseRestrainExpressions { get; set; }
/// <summary>
/// 布尔关报警文本
/// </summary>
public string? BoolCloseAlarmText { get; set; }
/// <summary>
/// 高报使能
/// </summary>
public bool HAlarmEnable { get; set; }
/// <summary>
/// 高报约束
/// </summary>
public string? HRestrainExpressions { get; set; }
/// <summary>
/// 高报文本
/// </summary>
public string? HAlarmText { get; set; }
/// <summary>
/// 高限值
/// </summary>
public double? HAlarmCode { get; set; }
/// <summary>
/// 高高报使能
/// </summary>
public bool HHAlarmEnable { get; set; }
/// <summary>
/// 高高报约束
/// </summary>
public string? HHRestrainExpressions { get; set; }
/// <summary>
/// 高高报文本
/// </summary>
public string? HHAlarmText { get; set; }
/// <summary>
/// 高高限值
/// </summary>
public double? HHAlarmCode { get; set; }
/// <summary>
/// 低报使能
/// </summary>
public bool LAlarmEnable { get; set; }
/// <summary>
/// 低报约束
/// </summary>
public string? LRestrainExpressions { get; set; }
/// <summary>
/// 低报文本
/// </summary>
public string? LAlarmText { get; set; }
/// <summary>
/// 低限值
/// </summary>
public double? LAlarmCode { get; set; }
/// <summary>
/// 低低报使能
/// </summary>
public bool LLAlarmEnable { get; set; }
/// <summary>
/// 低低报约束
/// </summary>
public string? LLRestrainExpressions { get; set; }
/// <summary>
/// 低低报文本
/// </summary>
public string? LLAlarmText { get; set; }
/// <summary>
/// 低低限值
/// </summary>
public double? LLAlarmCode { get; set; }
/// <summary>
/// 自定义报警使能
/// </summary>
public bool CustomAlarmEnable { get; set; }
/// <summary>
/// 自定义报警条件约束
/// </summary>
public string? CustomRestrainExpressions { get; set; }
/// <summary>
/// 自定义文本
/// </summary>
public string? CustomAlarmText { get; set; }
/// <summary>
/// 自定义报警条件
/// </summary>
public string? CustomAlarmCode { get; set; }
#endregion
#region
/// <summary>
/// 自定义
/// </summary>
public string? Remark1 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark2 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark3 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark4 { get; set; }
/// <summary>
/// 自定义
/// </summary>
public string? Remark5 { get; set; }
#endregion
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(RegisterAddress) && string.IsNullOrEmpty(OtherMethod))
{
yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(RegisterAddress), nameof(OtherMethod) });
}
if (HHAlarmEnable && HHAlarmCode == null)
{
yield return new ValidationResult("HHAlarmCode cannot be null when HHAlarmEnable is true", new[] { nameof(HHAlarmCode) });
}
if (HAlarmEnable && HAlarmCode == null)
{
yield return new ValidationResult("HAlarmCode cannot be null when HAlarmEnable is true", new[] { nameof(HAlarmCode) });
}
if (LAlarmEnable && LAlarmCode == null)
{
yield return new ValidationResult("LAlarmCode cannot be null when LAlarmEnable is true", new[] { nameof(LAlarmCode) });
}
if (LLAlarmEnable && LLAlarmCode == null)
{
yield return new ValidationResult("LLAlarmCode cannot be null when LLAlarmEnable is true", new[] { nameof(LLAlarmCode) });
}
if (HHAlarmEnable && HAlarmEnable && HHAlarmCode <= HAlarmCode)
{
yield return new ValidationResult("HHAlarmCode must be greater than HAlarmCode", new[] { nameof(HHAlarmCode), nameof(HAlarmCode) });
}
if (HAlarmEnable && LAlarmEnable && HAlarmCode <= LAlarmCode)
{
yield return new ValidationResult("HAlarmCode must be greater than LAlarmCode", new[] { nameof(HAlarmCode), nameof(LAlarmCode) });
}
if (LAlarmEnable && LLAlarmEnable && LAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("LAlarmCode must be greater than LLAlarmCode", new[] { nameof(LAlarmCode), nameof(LLAlarmCode) });
}
if (HHAlarmEnable && LAlarmEnable && HHAlarmCode <= LAlarmCode)
{
yield return new ValidationResult("HHAlarmCode should be greater than or less than LAlarmCode", new[] { nameof(HHAlarmCode), nameof(LAlarmCode) });
}
if (HHAlarmEnable && LLAlarmEnable && HHAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("HHAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HHAlarmCode), nameof(LLAlarmCode) });
}
if (HAlarmEnable && LLAlarmEnable && HAlarmCode <= LLAlarmCode)
{
yield return new ValidationResult("HAlarmCode should be greater than or less than LLAlarmCode", new[] { nameof(HAlarmCode), nameof(LLAlarmCode) });
}
}
}

View File

@@ -14,6 +14,8 @@ using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;
using ThingsGateway.NewLife.Extension;
using TouchSocket.Rpc;
namespace ThingsGateway.Gateway.Application;
/// <summary>
@@ -25,7 +27,9 @@ namespace ThingsGateway.Gateway.Application;
[ApiController]
[RolePermission]
[Authorize(AuthenticationSchemes = "Bearer")]
public class RuntimeInfoController : ControllerBase
[TouchSocket.WebApi.Router("/miniapi/[api]/[action]")]
[TouchSocket.WebApi.EnableCors("cors")]
public class RuntimeInfoController : ControllerBase, IRpcServer
{
/// <summary>
/// 获取冗余状态
@@ -33,6 +37,7 @@ public class RuntimeInfoController : ControllerBase
/// <returns></returns>
[HttpGet("redundancyStatus")]
[DisplayName("获取冗余状态")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
public bool GetRedundancyStatus()
{
return GlobalData.StartCollectChannelEnable;
@@ -44,7 +49,8 @@ public class RuntimeInfoController : ControllerBase
/// <returns></returns>
[HttpGet("channelList")]
[DisplayName("获取通道信息")]
public async Task<SqlSugarPagedList<ChannelRuntime>> GetChannelListAsync([FromQuery] ChannelPageInput input)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task<SqlSugarPagedList<ChannelRuntime>> GetChannelListAsync([FromQuery][TouchSocket.WebApi.FromBody] ChannelPageInput input)
{
var channelRuntimes = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
@@ -62,7 +68,8 @@ public class RuntimeInfoController : ControllerBase
/// <returns></returns>
[HttpGet("deviceList")]
[DisplayName("获取设备信息")]
public async Task<SqlSugarPagedList<DeviceRuntime>> GetDeviceListAsync([FromQuery] DevicePageInput input)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task<SqlSugarPagedList<DeviceRuntime>> GetDeviceListAsync([FromQuery][TouchSocket.WebApi.FromBody] DevicePageInput input)
{
var deviceRuntimes = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
var data = deviceRuntimes
@@ -80,9 +87,10 @@ public class RuntimeInfoController : ControllerBase
/// <returns></returns>
[HttpGet("realAlarmList")]
[DisplayName("获取实时报警变量信息")]
public async Task<SqlSugarPagedList<AlarmVariable>> GetRealAlarmList([FromQuery] AlarmVariablePageInput input)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task<SqlSugarPagedList<AlarmVariable>> GetRealAlarmList([FromQuery][TouchSocket.WebApi.FromBody] AlarmVariablePageInput input)
{
var realAlarmVariables = await GlobalData.GetCurrentUserRealAlarmVariables().ConfigureAwait(false);
var realAlarmVariables = await GlobalData.GetCurrentUserRealAlarmVariablesAsync().ConfigureAwait(false);
var data = realAlarmVariables
.WhereIF(!input.RegisterAddress.IsNullOrEmpty(), a => a.RegisterAddress == input.RegisterAddress)
@@ -98,7 +106,8 @@ public class RuntimeInfoController : ControllerBase
/// <returns></returns>
[HttpGet("variableList")]
[DisplayName("获取变量信息")]
public async Task<SqlSugarPagedList<VariableRuntime>> GetVariableList([FromQuery] VariablePageInput input)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public async Task<SqlSugarPagedList<VariableRuntime>> GetVariableList([FromQuery][TouchSocket.WebApi.FromBody] VariablePageInput input)
{
var variables = await GlobalData.GetCurrentUserIdVariables().ConfigureAwait(false);
var data = variables
@@ -117,6 +126,7 @@ public class RuntimeInfoController : ControllerBase
/// </summary>
[HttpGet("getPluginPropertys")]
[DisplayName("获取默认插件属性")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
public Dictionary<string, string> GetPluginPropertys(string pluginName)
{
var data = GlobalData.PluginService.GetDriverPropertyTypes(pluginName);
@@ -131,10 +141,11 @@ public class RuntimeInfoController : ControllerBase
/// </summary>
[HttpGet("getPluginInfos")]
[DisplayName("获取插件")]
public SqlSugarPagedList<PluginInfo> GetPluginInfos([FromQuery] PluginInfoPageInput input)
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public SqlSugarPagedList<PluginInfo> GetPluginInfos([FromQuery][TouchSocket.WebApi.FromBody] PluginInfoPageInput input)
{
//指定关键词搜索为插件FullName
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

@@ -59,10 +59,10 @@ public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
}
protected override void Dispose(bool disposing)
protected override Task DisposeAsync(bool disposing)
{
GlobalData.AlarmChangedEvent -= AlarmValueChange;
base.Dispose(disposing);
return base.DisposeAsync(disposing);
}
/// <summary>
/// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。

View File

@@ -130,7 +130,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
/// <summary>
/// 释放资源方法
/// </summary>
protected override void Dispose(bool disposing)
protected override Task DisposeAsync(bool disposing)
{
// 解绑事件
GlobalData.AlarmChangedEvent -= AlarmValueChange;
@@ -142,7 +142,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
_memoryDevModelQueue.Clear();
_memoryVarModelQueue.Clear();
_memoryVarModelsQueue.Clear();
base.Dispose(disposing);
return base.DisposeAsync(disposing);
}
/// <summary>

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

@@ -23,6 +23,7 @@ public class CacheDBItem<T> : IPrimaryIdEntity
}
[SugarColumn(IsPrimaryKey = true)]
[System.ComponentModel.DataAnnotations.Key]
public long Id { get; set; }
[SugarColumn(IsJson = true, ColumnDataType = "TEXT")]

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>
/// 插件配置项
@@ -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,33 +346,33 @@ 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)
{
if (state is not VariableSourceRead variableSourceRead) return;
if (Pause) return;
if (cancellationToken.IsCancellationRequested) return;
var readErrorCount = 0;
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)
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
var readResult = await ReadSourceAsync(variableSourceRead, allToken).ConfigureAwait(false);
var readErrorCount = 0;
// 读取失败时重试一定次数
while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount)
{
@@ -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)
{
@@ -688,19 +688,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver
{
// 调用方法并获取结果
var data = await variableMethod.InvokeMethodAsync(this, value, cancellationToken).ConfigureAwait(false);
result = new(data);
var operResultType = typeof(IOperResult<>);
var interfaceType = data.GetType().GetInterfaces()
.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == operResultType);
if (interfaceType != null)
{
var contentProperty = interfaceType.GetProperty("Content");
if (contentProperty != null)
{
result.Content = contentProperty.GetValue(data);
}
}
result = data.GetOperResult();
// 如果方法有返回值,并且是读取操作
if (method.HasReturn && isRead)
@@ -731,5 +720,12 @@ public abstract class CollectBase : DriverBase, IRpcDriver
}
}
#endregion
protected override Task DisposeAsync(bool disposing)
{
_linkedCtsCache?.SafeDispose();
return base.DisposeAsync(disposing);
}
}

View File

@@ -47,10 +47,11 @@ public abstract class CollectFoundationBase : CollectBase
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
protected override async Task DisposeAsync(bool disposing)
{
FoundationDevice?.Dispose();
base.Dispose(disposing);
if (FoundationDevice != null)
await FoundationDevice.SafeDisposeAsync().ConfigureAwait(false);
await base.DisposeAsync(disposing).ConfigureAwait(false);
}
/// <summary>
/// 开始通讯执行的方法

View File

@@ -35,6 +35,7 @@ public abstract class CollectPropertyBase : DriverPropertyBase
/// <summary>
/// 读写占空比
/// </summary>
[MinValue(1)]
public virtual int DutyCycle { get; set; } = 3;
}
@@ -52,6 +53,7 @@ public abstract class CollectPropertyRetryBase : CollectPropertyBase
public override int RetryCount { get; set; } = 3;
[DynamicProperty(Remark = "n 次写入操作会执行一次读取")]
[MinValue(1)]
public override int DutyCycle { get; set; } = 3;
}

View File

@@ -26,11 +26,12 @@ namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 插件基类
/// </summary>
public abstract class DriverBase : DisposableObject, IDriver
public abstract class DriverBase : AsyncDisposableObject, IDriver
{
/// <inheritdoc cref="DriverBase"/>
public DriverBase()
{
Localizer = App.CreateLocalizerByType(typeof(DriverBase))!;
}
@@ -39,8 +40,7 @@ public abstract class DriverBase : DisposableObject, IDriver
/// <summary>
/// 当前设备
/// </summary>
public DeviceRuntime? CurrentDevice => WeakReferenceCurrentDevice?.TryGetTarget(out var target) == true ? target : null;
private WeakReference<DeviceRuntime> WeakReferenceCurrentDevice { get; set; }
public DeviceRuntime? CurrentDevice { get; private set; }
/// <summary>
/// 当前设备Id
/// </summary>
@@ -208,7 +208,7 @@ public abstract class DriverBase : DisposableObject, IDriver
/// </summary>
internal void InitDevice(DeviceRuntime device)
{
WeakReferenceCurrentDevice = new WeakReference<DeviceRuntime>(device);
CurrentDevice = device;
_logger = App.RootServices.GetService<Microsoft.Extensions.Logging.ILoggerFactory>().CreateLogger($"Driver[{CurrentDevice.Name}]");
@@ -313,38 +313,45 @@ public abstract class DriverBase : DisposableObject, IDriver
protected abstract List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken);
protected object stopLock = new();
protected WaitLock stopLock = new(nameof(DriverBase));
/// <summary>
/// 已停止任务,释放插件
/// </summary>
internal virtual void Stop()
internal virtual async Task StopAsync()
{
if (!DisposedValue)
{
lock (stopLock)
await stopLock.WaitAsync().ConfigureAwait(false);
try
{
if (!DisposedValue)
{
try
{
// 执行资源释放操作
Dispose();
}
catch (Exception ex)
{
// 记录 Dispose 方法执行失败的错误信息
LogMessage?.LogError(ex, "Dispose");
}
// 记录设备线程已停止的信息
LogMessage?.LogInformation(string.Format(AppResource.DeviceTaskStop, DeviceName));
// 执行资源释放操作
await this.SafeDisposeAsync().ConfigureAwait(false);
}
}
catch (Exception ex)
{
// 记录 Dispose 方法执行失败的错误信息
LogMessage?.LogError(ex, "Dispose");
}
finally
{
stopLock.Release();
}
}
}
protected override void Dispose(bool disposing)
protected override async Task DisposeAsync(bool disposing)
{
base.Dispose(disposing);
await base.DisposeAsync(disposing).ConfigureAwait(false);
if (TaskSchedulerLoop != null)
{
lock (TaskSchedulerLoop)

View File

@@ -14,7 +14,7 @@ using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application
{
public interface IDriver : IDisposable
public interface IDriver : IAsyncDisposable
{
bool DisposedValue { get; }
ChannelRuntime CurrentChannel { get; }

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,13 +17,16 @@ 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>
@@ -32,6 +35,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[IgnoreExcel]
[AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)]
[System.ComponentModel.DataAnnotations.Key]
public virtual long Id { get; set; }
/// <summary>

View File

@@ -17,13 +17,16 @@ 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()

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,14 +16,17 @@ 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>
@@ -31,6 +34,7 @@ public class Variable : BaseDataEntity, IValidatableObject
/// </summary>
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
[AutoGenerateColumn(Visible = false, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false, Sortable = true, DefaultSort = true, DefaultSortOrder = SortOrder.Asc)]
[System.ComponentModel.DataAnnotations.Key]
public override long Id { get; set; }
/// <summary>
@@ -39,13 +43,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;
@@ -59,13 +59,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;
@@ -80,29 +73,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>
@@ -271,197 +252,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
@@ -509,30 +308,6 @@ public class Variable : BaseDataEntity, IValidatableObject
yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(OtherMethod), nameof(RegisterAddress) });
}
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

@@ -1,58 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application
{
internal static class USheetDataHelpers
{
public static USheetDatas GetUSheetDatas(Dictionary<string, object> data)
{
var uSheetDatas = new USheetDatas();
foreach (var a in data)
{
var value = (a.Value as IEnumerable<Dictionary<string, object>>).ToList();
var uSheetData = new USheetData();
uSheetData.id = a.Key;
uSheetData.name = a.Key;
for (int row1 = 0; row1 < value.Count; row1++)
{
if (row1 == 0)
{
Dictionary<int, USheetCelldata> usheetColldata = new();
int col = 0;
foreach (var colData in value[row1])
{
usheetColldata.Add(col, new USheetCelldata() { v = colData.Key });
col++;
}
uSheetData.cellData.Add(row1, usheetColldata);
}
{
Dictionary<int, USheetCelldata> usheetColldata = new();
int col = 0;
foreach (var colData in value[row1])
{
usheetColldata.Add(col, new USheetCelldata() { v = colData.Value });
col++;
}
uSheetData.cellData.Add(row1 + 1, usheetColldata);
}
}
uSheetData.rowCount = uSheetData.cellData.Count + 100;
uSheetData.columnCount = uSheetData.cellData.FirstOrDefault().Value?.Count ?? 0;
uSheetDatas.sheets.Add(a.Key, uSheetData);
}
return uSheetDatas;
}
}
}

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

@@ -27,7 +27,7 @@ public class LogJob : IJob
await DeleteRpcLog(rpcLogDaysdaysAgo, stoppingToken).ConfigureAwait(false);
await DeleteBackendLog(backendLogdaysAgo, stoppingToken).ConfigureAwait(false);
await DeleteTextLog(stoppingToken).ConfigureAwait(false);
await DeleteLocalDB(stoppingToken).ConfigureAwait(false);
DeleteLocalDB(stoppingToken);
}
private static async Task DeleteRpcLog(int daysAgo, CancellationToken stoppingToken)
@@ -49,8 +49,8 @@ public class LogJob : IJob
//网关通道日志以通道id命名
var channelService = App.RootServices.GetService<IChannelService>();
var deviceService = App.RootServices.GetService<IDeviceService>();
var channelNames = (await channelService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet();
var deviceNames = (await deviceService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet();
var channelNames = (GlobalData.Channels.Keys).ToHashSet();
var deviceNames = (GlobalData.Devices.Keys).ToHashSet();
var channelBaseDir = LoggerExtensions.GetChannelLogBasePath();
Directory.CreateDirectory(channelBaseDir);
var deviceBaseDir = LoggerExtensions.GetDeviceLogBasePath();
@@ -112,10 +112,9 @@ public class LogJob : IJob
}
}
public async Task DeleteLocalDB(CancellationToken stoppingToken)
public void DeleteLocalDB(CancellationToken stoppingToken)
{
var deviceService = App.RootServices.GetService<IDeviceService>();
var data = (await deviceService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name).ToHashSet();
var data = (GlobalData.Devices.Keys).ToHashSet();
var dir = CacheDBUtil.GetCacheFileBasePath();
string[] dirs = Directory.GetDirectories(dir);
foreach (var item in dirs)

View File

@@ -1,6 +1,32 @@
{
"ThingsGateway.Management.Application.ExportString": {
"ThingsGateway.Gateway.Application.DefaultDiagram": {
"ManagementConfigName": "ManagementConfigName"
},
"ThingsGateway.Management.Application.ManagementConfig": {
"Name": "Name",
"ServerUri": "ServerUri",
"Enable": "Enable",
"IsServer": "IsServer",
"VerifyToken": "VerifyToken",
"HeartbeatInterval": "HeartbeatInterval",
"ImportNullError": "Unable to recognize"
},
"ThingsGateway.Management.Application.UpdateZipFile": {
"AppName": "AppName",
"Architecture": "Architecture",
"DotNetVersion": "DotNetVersion",
"FilePath": "FilePath",
"FileSize": "FileSize",
"MinimumCompatibleVersion": "MinimumCompatibleVersion",
"OSPlatform": "OSPlatform",
"Version": "Version"
},
"ThingsGateway.Gateway.Application.INode": {
"Actuator": "Actuator",
"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",
@@ -375,6 +400,7 @@
"BusinessDeviceName": "BusinessDevice",
"ChannelName": "Channel",
"DeviceName": "Device",
"AlarmName": "Alarm",
"RedundantDeviceName": "Redundant Device",
"VariableName": "Variable"
},
@@ -436,24 +462,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 +482,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 +511,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 +557,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.ExportString": {
"ThingsGateway.Gateway.Application.DefaultDiagram": {
"ManagementConfigName": "通讯配置"
},
"ThingsGateway.Management.Application.ManagementConfig": {
"Name": "名称",
"ServerUri": "服务端Url",
"Enable": "启用",
"IsServer": "服务端",
"VerifyToken": "验证令牌",
"HeartbeatInterval": "心跳间隔",
"ImportNullError": "无法识别"
},
"ThingsGateway.Management.Application.UpdateZipFile": {
"AppName": "名称",
"Architecture": "架构",
"DotNetVersion": ".net版本",
"FilePath": "文件路径",
"FileSize": "文件大小",
"MinimumCompatibleVersion": "最小兼容版本",
"OSPlatform": "系统版本",
"Version": "版本"
},
"ThingsGateway.Gateway.Application.INode": {
"Actuator": "执行",
"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": "高报使能",
@@ -376,6 +404,7 @@
"BusinessDeviceName": "业务设备",
"ChannelName": "通道",
"DeviceName": "设备",
"AlarmName": "报警",
"RedundantDeviceName": "冗余设备",
"VariableName": "变量"
},
@@ -438,6 +467,7 @@
"ThingsGateway.Gateway.Application.Variable": {
"AddressOrOtherMethodNotNull": " 变量地址或特殊方法不能同时为空 ",
"AlarmDelay": "报警延时",
"AlarmLevel": "报警等级",
"ArrayLength": "数组长度",
"BoolCloseAlarmEnable": "布尔关报警使能",
"BoolCloseAlarmText": "布尔关报警文本",
@@ -506,6 +536,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 +584,8 @@
"DeviceName": "设备名称",
"DynamicVariable": "动态变量",
"EventTime": "事件时间",
"ConfirmTime": "确认时间",
"FinishTime": "恢复时间",
"EventType": "事件类型",
"IntervalTime.MinValue": " {0} 值太小",
"IsOnline": "在线",

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