Compare commits

...

21 Commits

Author SHA1 Message Date
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
Diego
fba0723a6d 更新依赖 2025-07-31 18:39:46 +08:00
Diego
2db3f78f0c 添加 控制写操作与读操作的比率 的插件配置属性 2025-07-31 18:18:41 +08:00
Diego
badf61fe01 10.9.99 2025-07-31 16:25:47 +08:00
Diego
d74e0952dc 10.9.98 2025-07-31 15:57:33 +08:00
345 changed files with 4601 additions and 5775 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

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

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

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

@@ -31,11 +31,12 @@
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="TDengine.Connector" Version="3.1.7" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.20" />
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.21" />
<PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
</ItemGroup>
</Project>

View File

@@ -1,14 +1,15 @@
<Project>
<PropertyGroup>
<PluginVersion>10.9.93</PluginVersion>
<ProPluginVersion>10.9.93</ProPluginVersion>
<DefaultVersion>10.9.97</DefaultVersion>
<AuthenticationVersion>2.9.29</AuthenticationVersion>
<SourceGeneratorVersion>10.9.29</SourceGeneratorVersion>
<NET8Version>8.0.18</NET8Version>
<NET9Version>9.0.7</NET9Version>
<PluginVersion>10.10.9</PluginVersion>
<ProPluginVersion>10.10.9</ProPluginVersion>
<DefaultVersion>10.10.12</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.GetLogFiles(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.LastLogData(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.GetLogFiles(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.GetLogFiles(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

@@ -58,7 +58,7 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
{
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString() : byteBlock.ToString(byteBlock.Position))}");
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
try
{
@@ -172,7 +172,7 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
{
cancellationToken.ThrowIfCancellationRequested();
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString() : (memory.Span.ToString(Encoding.UTF8)))}");
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
//发送
await GoSendAsync(memory, cancellationToken).ConfigureAwait(false);
@@ -191,7 +191,7 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
{
sendMessage.Build(ref byteBlock);
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
//非并发主从协议
if (IsSingleThread)
{

View File

@@ -65,7 +65,7 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
byteBlock.Position = 0;
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString() : byteBlock.ToString(byteBlock.Position))}");
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
TRequest request = null;
if (IsSingleThread)
@@ -151,7 +151,7 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
cancellationToken.ThrowIfCancellationRequested();
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString() : (memory.Span.ToString(Encoding.UTF8)))}");
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
//发送
await GoSendAsync(endPoint, memory, cancellationToken).ConfigureAwait(false);
}
@@ -169,7 +169,7 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
{
sendMessage.Build(ref byteBlock);
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{endPoint}- Send:{(IsHexLog ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
Logger?.Trace($"{endPoint}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
if (IsSingleThread)
{

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; }
@@ -446,7 +446,13 @@ public abstract class DeviceBase : DisposableObject, IDevice
public virtual OperResult<IClientChannel> GetChannel(string socketId)
{
if (string.IsNullOrWhiteSpace(socketId))
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
{
if (Channel is IClientChannel clientChannel)
return new OperResult<IClientChannel>() { Content = clientChannel };
else
return new OperResult<IClientChannel>("The communication link cannot be obtained, DtuId must be set!");
}
if (Channel is ITcpServiceChannel serviceChannel)
{
@@ -464,7 +470,12 @@ public abstract class DeviceBase : DisposableObject, IDevice
}
}
else
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
{
if (Channel is IClientChannel clientChannel)
return new OperResult<IClientChannel>() { Content = clientChannel };
else
return new OperResult<IClientChannel>("The communication link cannot be obtained!");
}
}
/// <inheritdoc/>
@@ -542,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);
@@ -564,12 +575,18 @@ public abstract class DeviceBase : DisposableObject, IDevice
var sendOperResult = await SendAsync(command, clientChannel, endPoint, cancellationToken).ConfigureAwait(false);
if (!sendOperResult.IsSuccess)
throw sendOperResult.Exception ?? new(sendOperResult.ErrorMessage ?? "unknown error");
return new MessageBase(sendOperResult);
waitData.SetCancellationToken(cancellationToken);
await waitData.WaitAsync(timeout).ConfigureAwait(false);
try
{
waitData.SetCancellationToken(Channel.ClosedToken);
await waitData.WaitAsync(timeout).ConfigureAwait(false);
}
catch (Exception ex)
{
return new MessageBase(ex);
}
var result = waitData.Check();
if (result.IsSuccess)
{
@@ -577,33 +594,11 @@ public abstract class DeviceBase : DisposableObject, IDevice
}
else
{
if (cancellationToken.IsCancellationRequested && result.Exception is OperationCanceledException)
{
waitData.Reset();
waitData.SetCancellationToken(CancellationToken.None);
await waitData.WaitAsync(timeout).ConfigureAwait(false);
result = waitData.Check();
if (result.IsSuccess)
{
return waitData.WaitResult;
}
else
{
return new MessageBase(result);
}
}
return new MessageBase(result);
}
}
catch (Exception ex)
{
if (cancellationToken.IsCancellationRequested)
{
if (!this.DisposedValue)
{
await Task.Delay(timeout, CancellationToken.None).ConfigureAwait(false);
}
}
return new MessageBase(ex);
}
finally
@@ -1039,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)
{
@@ -1056,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

@@ -252,7 +252,7 @@ public static class ByteExtensions
/// 字节数组默认转16进制字符
/// </summary>
/// <returns></returns>
public static string ToHexString(this ArraySegment<byte> buffer, char splite = ' ', int newLineCount = 0)
public static string ToHexString(this ArraySegment<byte> buffer, char splite = default, int newLineCount = 0)
{
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
}
@@ -261,7 +261,7 @@ public static class ByteExtensions
/// 字节数组默认转16进制字符
/// </summary>
/// <returns></returns>
public static string ToHexString(this byte[] buffer, char splite = ' ', int newLineCount = 0)
public static string ToHexString(this byte[] buffer, char splite = default, int newLineCount = 0)
{
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
}
@@ -269,7 +269,7 @@ public static class ByteExtensions
/// 字节数组默认转16进制字符
/// </summary>
/// <returns></returns>
public static string ToHexString(this Span<byte> buffer, char splite = ' ', int newLineCount = 0)
public static string ToHexString(this Span<byte> buffer, char splite = default, int newLineCount = 0)
{
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
}
@@ -277,7 +277,7 @@ public static class ByteExtensions
/// 字节数组默认转16进制字符
/// </summary>
/// <returns></returns>
public static string ToHexString(this ReadOnlySpan<byte> buffer, char splite = ' ', int newLineCount = 0)
public static string ToHexString(this ReadOnlySpan<byte> buffer, char splite = default, int newLineCount = 0)
{
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
}

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>> GetLogFiles(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>> LastLogData(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(LastLogData)}_{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>>> GetLogFiles(string directoryPath);
public Task<OperResult<List<LogData>>> LastLogData(string file, int lineCount = 200);
}

View File

@@ -8,13 +8,13 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Riok.Mapperly.Abstractions;
using TouchSocket.Dmtp;
namespace ThingsGateway.Upgrade;
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
public static partial class UpgradeMapper
namespace ThingsGateway.Foundation;
public class TextFileReadService : ITextFileReadService
{
public static partial List<TcpSessionClientDto> AdaptListTcpSessionClientDto(this List<TcpDmtpSessionClient> src);
public Task<OperResult<List<string>>> GetLogFiles(string directoryPath) => Task.FromResult(TextFileReader.GetLogFiles(directoryPath));
public Task<OperResult<List<LogData>>> LastLogData(string file, int lineCount = 200) => Task.FromResult(TextFileReader.LastLogData(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

@@ -14,48 +14,88 @@ namespace ThingsGateway.Gateway.Application;
public class AsyncReadWriteLock
{
private readonly int _writeReadRatio = 3; // 写3次会允许1次读但写入也不会被阻止具体协议取决于插件协议实现
public AsyncReadWriteLock(int writeReadRatio)
{
_writeReadRatio = writeReadRatio;
}
private AsyncAutoResetEvent _readerLock = new AsyncAutoResetEvent(false); // 控制读计数
private long _writerCount = 0; // 当前活跃的写线程数
private long _readerCount = 0; // 当前被阻塞的读线程数
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
/// <summary>
/// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。
/// </summary>
public async Task<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken)
{
if (Interlocked.Read(ref _writerCount) > 0)
{
Interlocked.Increment(ref _readerCount);
// 第一个读者需要获取写入锁,防止写操作
await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
Interlocked.Decrement(ref _readerCount);
}
return _cancellationTokenSource.Token;
}
public bool WriteWaited => _writerCount > 0;
/// <summary>
/// 获取写锁,阻止所有读取。
/// </summary>
public IDisposable WriterLock()
public async Task<IDisposable> WriterLockAsync(CancellationToken cancellationToken)
{
if (Interlocked.Increment(ref _writerCount) == 1)
{
var cancellationTokenSource = _cancellationTokenSource;
_cancellationTokenSource = new();
cancellationTokenSource.Cancel();//取消读取
await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取
cancellationTokenSource.SafeDispose();
}
return new Writer(this);
}
private object lockObject = new();
private void ReleaseWriter()
{
if (Interlocked.Decrement(ref _writerCount) == 0)
var writerCount = Interlocked.Decrement(ref _writerCount);
if (writerCount == 0)
{
var resetEvent = _readerLock;
_readerLock = new(false);
Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
resetEvent.SetAll();
}
else
{
lock (lockObject)
{
// 读写占空比, 用于控制写操作与读操作的比率。该比率 n 次写入操作会执行一次读取操作。即使在应用程序执行大量的连续写入操作时,也必须确保足够的读取数据处理时间。相对于更加均衡的读写数据流而言,该特点使得外部写入可连续无顾忌操作
if (_writeReadRatio > 0)
{
var count = Interlocked.Increment(ref _writeSinceLastReadCount);
if (count >= _writeReadRatio)
{
Interlocked.Exchange(ref _writeSinceLastReadCount, 0);
_readerLock.Set();
}
}
else
{
_readerLock.Set();
}
}
}
}
private int _writeSinceLastReadCount = 0;
private struct Writer : IDisposable
{
private readonly AsyncReadWriteLock _lock;

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

@@ -21,6 +21,7 @@ using System.IO.Ports;
using ThingsGateway.FriendlyException;
using TouchSocket.Core;
using TouchSocket.Rpc;
using TouchSocket.Sockets;
namespace ThingsGateway.Gateway.Application;
@@ -34,7 +35,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 +46,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 +58,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 +71,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 +91,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 +104,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 +116,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 +135,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,7 +155,8 @@ 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<ChannelInput> channels, ItemChangedType type, bool restart = true)
{
return GlobalData.ChannelRuntimeService.BatchSaveChannelAsync(channels.AdaptListChannel(), type, restart);
}
@@ -155,7 +166,8 @@ 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<DeviceInput> devices, ItemChangedType type, bool restart = true)
{
return GlobalData.DeviceRuntimeService.BatchSaveDeviceAsync(devices.AdaptListDevice(), type, restart);
}
@@ -165,7 +177,8 @@ 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<VariableInput> variables, ItemChangedType type, bool restart = true)
{
return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables.AdaptListVariable(), type, restart, default);
}
@@ -175,7 +188,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 +200,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 +212,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 +224,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 +237,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))

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,7 +87,8 @@ 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);
@@ -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.GetPluginListSync()).WhereIF(!input.Name.IsNullOrWhiteSpace(), a => a.Name == input.Name)
.ToPagedList(input);
}
}

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

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

@@ -194,6 +194,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver
// 从插件服务中获取当前设备关联的驱动方法信息列表
DriverMethodInfos = GlobalData.PluginService.GetDriverMethodInfos(device.PluginName, this);
ReadWriteLock = new(CollectProperties.DutyCycle);
}
public virtual string GetAddressDescription()
@@ -351,15 +353,13 @@ public abstract class CollectBase : DriverBase, IRpcDriver
async Task ReadVariableSource(object? state, CancellationToken cancellationToken)
{
var readToken = await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false);
if (state is not VariableSourceRead variableSourceRead) return;
if (Pause) return;
if (cancellationToken.IsCancellationRequested) return;
var readErrorCount = 0;
var readToken = await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false);
if (readToken.IsCancellationRequested)
{
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
@@ -373,6 +373,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver
// 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)
{
@@ -474,7 +476,7 @@ public abstract class CollectBase : DriverBase, IRpcDriver
/// <returns></returns>
protected abstract Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables);
protected AsyncReadWriteLock ReadWriteLock = new();
protected AsyncReadWriteLock ReadWriteLock;
/// <summary>
/// 采集驱动读取,读取成功后直接赋值变量
@@ -565,7 +567,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver
ConcurrentDictionary<string, OperResult<object>> operResults = new();
using var writeLock = ReadWriteLock.WriterLock();
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
var list = writeInfoLists
.Where(a => !results.Any(b => b.Key == a.Key.Name))
.ToDictionary(item => item.Key, item => item.Value).ToArray();
@@ -685,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)
@@ -728,5 +720,6 @@ public abstract class CollectBase : DriverBase, IRpcDriver
}
}
#endregion
}

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>
/// 开始通讯执行的方法
@@ -175,7 +176,7 @@ public abstract class CollectFoundationBase : CollectBase
/// <returns></returns>
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
{
using var writeLock = ReadWriteLock.WriterLock();
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
// 检查协议是否为空,如果为空则抛出异常
if (FoundationDevice == null)
throw new NotSupportedException();

View File

@@ -31,6 +31,12 @@ public abstract class CollectPropertyBase : DriverPropertyBase
/// 失败重试次数默认3
/// </summary>
public virtual int RetryCount { get; set; } = 3;
/// <summary>
/// 读写占空比
/// </summary>
[MinValue(1)]
public virtual int DutyCycle { get; set; } = 3;
}
/// <summary>
@@ -45,4 +51,9 @@ public abstract class CollectPropertyRetryBase : CollectPropertyBase
/// </summary>
[DynamicProperty]
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

@@ -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>
@@ -46,6 +50,7 @@ public class Variable : BaseDataEntity, IValidatableObject
private long deviceId;
private int? arrayLength;
private int alarmDelay;
private int alarmLevel;
private ProtectTypeEnum protectType = ProtectTypeEnum.ReadWrite;
private DataTypeEnum dataType = DataTypeEnum.Int16;
@@ -272,6 +277,15 @@ public class Variable : BaseDataEntity, IValidatableObject
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 AlarmLevel { get => alarmLevel; set => alarmLevel = value; }
/// <summary>
/// 报警延时
/// </summary>

View File

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

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

@@ -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,24 @@
{
"ThingsGateway.Management.Application._Imports": {
"Restart": "Restart",
"Upgrade": "Upgrade"
},
"ThingsGateway.Management.Application.SaveUpdateZipFile": {
"DownTemplate": "Download Template",
"SaveUpdateZipFile": "Upload Version Package"
},
"ThingsGateway.Management.Application.UpdateZipFile": {
"AppName": "AppName",
"Architecture": "Architecture",
"DotNetVersion": "DotNetVersion",
"FilePath": "FilePath",
"FileSize": "FileSize",
"MinimumCompatibleVersion": "MinimumCompatibleVersion",
"OSPlatform": "OSPlatform",
"Version": "Version"
},
"ThingsGateway.Gateway.Application.DefaultDiagram": {
"ThingsGateway.Gateway.Application.INode": {
"Actuator": "Actuator",
"AlarmChangedTriggerNode": "AlarmStateTrigger",
@@ -53,11 +71,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 +81,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 +100,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 +120,7 @@
"ThingsGateway.Gateway.Application.AlarmVariable": {
"AlarmCode": "AlarmCode",
"AlarmDelay": "AlarmDelay",
"AlarmLevel": "AlarmLevel",
"AlarmEnable": "AlarmEnable",
"AlarmLimit": "AlarmLimit",
"AlarmText": "AlarmText",
@@ -295,7 +310,8 @@
"RetryCount": "RetryCount"
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"RetryCount": "RetryCount"
"RetryCount": "RetryCount",
"DutyCycle": "DutyCycle"
},
"ThingsGateway.Gateway.Application.ControlController": {
"BatchSaveChannelAsync": "BatchSaveChannel",
@@ -436,6 +452,7 @@
"ThingsGateway.Gateway.Application.Variable": {
"AddressOrOtherMethodNotNull": "Variable address or special method cannot be empty at the same time",
"AlarmDelay": "AlarmDelay",
"AlarmLevel": "AlarmLevel",
"ArrayLength": "ArrayLength",
"BoolCloseAlarmEnable": "BoolCloseAlarmEnable",
"BoolCloseAlarmText": "BoolCloseAlarmText",

View File

@@ -1,6 +1,24 @@
{
"ThingsGateway.Management.Application._Imports": {
"Restart": "重启",
"Upgrade": "更新"
},
"ThingsGateway.Management.Application.SaveUpdateZipFile": {
"DownTemplate": "下载模板",
"SaveUpdateZipFile": "上传版本包"
},
"ThingsGateway.Management.Application.UpdateZipFile": {
"AppName": "名称",
"Architecture": "架构",
"DotNetVersion": ".net版本",
"FilePath": "文件路径",
"FileSize": "文件大小",
"MinimumCompatibleVersion": "最小兼容版本",
"OSPlatform": "系统版本",
"Version": "版本"
},
"ThingsGateway.Gateway.Application.DefaultDiagram": {
"ThingsGateway.Gateway.Application.INode": {
"Actuator": "执行",
"AlarmChangedTriggerNode": "报警状态触发器",
@@ -51,11 +69,8 @@
"RulesId": "名称"
},
"ThingsGateway.Management.AutoUpdateController": {
"AutoUpdateController": "程序更新",
"Update": "更新"
},
"ThingsGateway.Management.RedundancyHostedService": {
"ThingsGateway.Gateway.Application.RedundancyHostedService": {
"ErrorSynchronizingData": "同步数据到从站错误",
"RedundancyDisable": "不启用网关冗余站点",
"RedundancyDup": "主备站设置重复",
@@ -65,10 +80,10 @@
"SwitchNormalState": "本机(主站)将切换到正常状态",
"SwitchSlaveState": "主站已恢复,本机(从站)将切换到备用状态"
},
"ThingsGateway.Management.RedundancyOptions": {
"ThingsGateway.Gateway.Application.RedundancyOptions": {
"Confirm": "确认切换冗余状态",
"Enable": "启用双机冗余",
"ForcedSync": "强制同步",
"RedundancyForcedSync": "强制同步",
"ForcedSyncWarning": "强制同步会生成数据库配置信息,是否继续?",
"HeartbeatInterval": "心跳间隔",
"IsMaster": "是否为主站",
@@ -84,13 +99,13 @@
"SyncInterval": "数据同步间隔",
"VerifyToken": "Token"
},
"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 +120,7 @@
"ThingsGateway.Gateway.Application.AlarmVariable": {
"AlarmCode": "报警值",
"AlarmDelay": "报警延时",
"AlarmLevel": "报警等级",
"AlarmEnable": "报警使能",
"AlarmLimit": "报警限值",
"AlarmText": "报警文本",
@@ -294,7 +310,8 @@
"RetryCount": "失败重试次数"
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"RetryCount": "失败重试次数"
"RetryCount": "失败重试次数",
"DutyCycle": "占空比"
},
"ThingsGateway.Gateway.Application.ControlController": {
"BatchSaveChannelAsync": "保存通道",
@@ -437,6 +454,7 @@
"ThingsGateway.Gateway.Application.Variable": {
"AddressOrOtherMethodNotNull": " 变量地址或特殊方法不能同时为空 ",
"AlarmDelay": "报警延时",
"AlarmLevel": "报警等级",
"ArrayLength": "数组长度",
"BoolCloseAlarmEnable": "布尔关报警使能",
"BoolCloseAlarmText": "布尔关报警文本",

View File

@@ -10,8 +10,6 @@
using Riok.Mapperly.Abstractions;
using ThingsGateway.Management;
namespace ThingsGateway.Gateway.Application;
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, RequiredMappingStrategy = RequiredMappingStrategy.None)]
public static partial class GatewayMapper

View File

@@ -63,6 +63,11 @@ public class AlarmVariable : PrimaryIdEntity, IDBHistoryAlarm
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public DataTypeEnum DataType { get; set; }
/// <inheritdoc cref="Variable.AlarmLevel"/>
[SugarColumn(ColumnDescription = "报警等级", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public int AlarmLevel { get; set; }
/// <inheritdoc cref="VariableRuntime.AlarmCode"/>
[SugarColumn(ColumnDescription = "报警值", IsNullable = false)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]

View File

@@ -117,7 +117,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
public void Init()
{
// 通过插件名称获取插件信息
PluginInfo = GlobalData.PluginService.GetList().FirstOrDefault(A => A.FullName == PluginName);
PluginInfo = GlobalData.PluginService.GetPluginListSync().FirstOrDefault(A => A.FullName == PluginName);
GlobalData.IdChannels.TryRemove(Id, out _);
GlobalData.Channels.TryRemove(Name, out _);

View File

@@ -27,7 +27,7 @@ public class PluginInfo
/// 插件文件名称.插件类型名称
/// </summary>
[AutoGenerateColumn(Ignore = true)]
public string FullName => PluginServiceUtil.GetFullName(FileName, Name);
public string FullName => PluginInfoUtil.GetFullName(FileName, Name);
/// <summary>
/// 插件文件名称
@@ -70,8 +70,5 @@ public class PluginInfo
/// </summary>
[IgnoreExcel]
[SugarColumn(IsIgnore = true)]
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
[AutoGenerateColumn(Ignore = true)]
public string Directory { get; set; }
}

View File

@@ -46,6 +46,8 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
private object _value;
private object lastSetValue;
private object rawValue;
internal object AlarmLockObject = new();
internal bool AlarmConfirm;
private DeviceRuntime? deviceRuntime;
private IVariableSource? variableSource;
private VariableMethod? variableMethod;

View File

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

View File

@@ -12,9 +12,10 @@ using Microsoft.Extensions.Logging;
using ThingsGateway.Common.Extension;
using ThingsGateway.Gateway.Application.Extensions;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application;
/// <summary>
@@ -44,7 +45,7 @@ internal sealed class AlarmTask : IDisposable
public void Dispose()
{
StopTask();
scheduledTask?.TryDispose();
scheduledTask?.SafeDispose();
}
#region
@@ -217,7 +218,7 @@ internal sealed class AlarmTask : IDisposable
if (alarmEnum == null)
{
// 如果仍未获取到报警类型,则触发需恢复报警事件(如果存在)
AlarmChange(item, null, text, EventTypeEnum.Finish, alarmEnum, delay);
AlarmChange(item, null, text, true, alarmEnum, delay);
}
else
{
@@ -232,14 +233,14 @@ internal sealed class AlarmTask : IDisposable
if (result)
{
// 如果表达式结果为true则触发报警事件
AlarmChange(item, limit, text, EventTypeEnum.Alarm, alarmEnum, delay);
AlarmChange(item, limit, text, false, alarmEnum, delay);
}
}
}
else
{
// 如果不存在报警约束表达式,则直接触发报警事件
AlarmChange(item, limit, text, EventTypeEnum.Alarm, alarmEnum, delay);
AlarmChange(item, limit, text, false, alarmEnum, delay);
}
}
}
@@ -250,193 +251,227 @@ internal sealed class AlarmTask : IDisposable
/// <param name="item">要处理的变量</param>
/// <param name="limit">报警限制值</param>
/// <param name="text">报警文本</param>
/// <param name="eventEnum">报警事件类型枚举</param>
/// <param name="finish">是否恢复</param>
/// <param name="alarmEnum">报警类型枚举</param>
/// <param name="delay">报警延时</param>
private static void AlarmChange(VariableRuntime item, object limit, string text, EventTypeEnum eventEnum, AlarmTypeEnum? alarmEnum, int delay)
private static void AlarmChange(VariableRuntime item, object limit, string text, bool finish, AlarmTypeEnum? alarmEnum, int delay)
{
bool changed = false;
if (eventEnum == EventTypeEnum.Finish)
lock (item.AlarmLockObject)
{
// 如果是需恢复报警事件
// 如果实时报警列表中不存在该变量,则直接返回
if (!GlobalData.RealAlarmIdVariables.ContainsKey(item.Id))
bool changed = false;
if (finish)
{
return;
}
}
else if (eventEnum == EventTypeEnum.Alarm)
{
// 如果是触发报警事件
// 在实时报警列表中查找该变量
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var variable))
{
// 如果变量已经处于相同的报警类型,则直接返回
if (item.AlarmType == alarmEnum)
// 如果是需恢复报警事件
// 如果实时报警列表中不存在该变量,则直接返回
if (!GlobalData.RealAlarmIdVariables.ContainsKey(item.Id))
{
return;
}
}
// 更新变量的报警信息和事件时间
if (eventEnum == EventTypeEnum.Alarm)
{
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
if (item.EventType != EventTypeEnum.Alarm && item.EventType != EventTypeEnum.PrepareAlarm)
{
item.EventType = EventTypeEnum.PrepareAlarm;//准备报警
item.PrepareAlarmEventTime = now;
}
else
{
if (item.EventType == EventTypeEnum.PrepareAlarm)
{
if ((now - item.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
//超过延时时间,触发报警
item.EventType = EventTypeEnum.Alarm;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else if (item.EventType == EventTypeEnum.Alarm && item.AlarmType != alarmEnum)
{
//报警类型改变,重新计时
if (item.PrepareAlarmEventTime == null)
item.PrepareAlarmEventTime = now;
if ((now - item.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
//超过延时时间,触发报警
item.EventType = EventTypeEnum.Alarm;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else
{
return;
}
}
}
else
{
if (item.EventType != EventTypeEnum.Confirm)
item.AlarmConfirm = false;
// 如果是触发报警事件
item.EventType = eventEnum;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else if (eventEnum == EventTypeEnum.Finish)
{
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
if (item.EventType != EventTypeEnum.Finish && item.EventType != EventTypeEnum.PrepareFinish)
// 在实时报警列表中查找该变量
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var variable))
{
item.EventType = EventTypeEnum.PrepareFinish;//准备报警
item.PrepareFinishEventTime = now;
// 如果变量已经处于相同的报警类型,则直接返回
if (item.AlarmType == alarmEnum)
return;
}
else
}
// 更新变量的报警信息和事件时间
if (!finish)
{
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
if (item.EventType == EventTypeEnum.PrepareFinish)
if (item.EventType != EventTypeEnum.Alarm && item.EventType != EventTypeEnum.PrepareAlarm)
{
if ((now - item.PrepareFinishEventTime!.Value).TotalMilliseconds > delay)
item.EventType = EventTypeEnum.PrepareAlarm;//准备报警
item.PrepareAlarmEventTime = now;
}
else
{
if (item.EventType == EventTypeEnum.PrepareAlarm)
{
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
if ((now - item.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
item.AlarmType = oldAlarm.AlarmType;
item.EventType = eventEnum;
item.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmCode = oldAlarm.AlarmCode;
item.RecoveryCode = item.Value.ToString();
item.AlarmText = oldAlarm.AlarmText;
item.EventTime = DateTime.Now;
item.PrepareFinishEventTime = null;
//超过延时时间,触发报警
item.EventType = EventTypeEnum.Alarm;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else if (item.EventType == EventTypeEnum.Alarm && item.AlarmType != alarmEnum)
{
//报警类型改变,重新计时
if (item.PrepareAlarmEventTime == null)
item.PrepareAlarmEventTime = now;
if ((now - item.PrepareAlarmEventTime!.Value).TotalMilliseconds > delay)
{
//超过延时时间,触发报警
item.EventType = EventTypeEnum.Alarm;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else
{
return;
}
}
else
{
return;
}
}
else
{
// 如果是触发报警事件
item.EventType = EventTypeEnum.Alarm;
item.AlarmTime = now;
item.EventTime = now;
item.AlarmType = alarmEnum;
item.AlarmLimit = limit.ToString();
item.AlarmCode = item.Value.ToString();
item.RecoveryCode = string.Empty;
item.AlarmText = text;
item.PrepareAlarmEventTime = null;
changed = true;
}
}
else
{
// 如果是需恢复报警事件
// 获取旧的报警信息
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
var now = DateTime.Now;
//添加报警延时策略
if (delay > 0)
{
item.AlarmType = oldAlarm.AlarmType;
item.EventType = eventEnum;
item.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmCode = oldAlarm.AlarmCode;
item.RecoveryCode = item.Value.ToString();
item.AlarmText = oldAlarm.AlarmText;
item.EventTime = DateTime.Now;
item.PrepareFinishEventTime = null;
changed = true;
if (item.EventType != EventTypeEnum.Finish && item.EventType != EventTypeEnum.PrepareFinish)
{
item.EventType = EventTypeEnum.PrepareFinish;
item.PrepareFinishEventTime = now;
}
else
{
if (item.EventType == EventTypeEnum.PrepareFinish)
{
if ((now - item.PrepareFinishEventTime!.Value).TotalMilliseconds > delay)
{
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
{
item.AlarmType = oldAlarm.AlarmType;
item.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmCode = oldAlarm.AlarmCode;
item.RecoveryCode = item.Value.ToString();
item.AlarmText = oldAlarm.AlarmText;
if (item.EventType != EventTypeEnum.Finish)
{
item.EventTime = now;
}
item.EventType = EventTypeEnum.Finish;
item.PrepareFinishEventTime = null;
changed = true;
}
}
}
else
{
return;
}
}
}
else
{
// 如果是需恢复报警事件
// 获取旧的报警信息
if (GlobalData.RealAlarmIdVariables.TryGetValue(item.Id, out var oldAlarm))
{
item.AlarmType = oldAlarm.AlarmType;
item.AlarmLimit = oldAlarm.AlarmLimit;
item.AlarmCode = oldAlarm.AlarmCode;
item.RecoveryCode = item.Value.ToString();
item.AlarmText = oldAlarm.AlarmText;
if (item.EventType != EventTypeEnum.Finish)
{
item.EventTime = now;
}
item.EventType = EventTypeEnum.Finish;
item.PrepareFinishEventTime = null;
changed = true;
}
}
}
}
// 触发报警变化事件
if (changed)
{
if (item.EventType == EventTypeEnum.Alarm)
// 触发报警变化事件
if (changed)
{
// 如果是触发报警事件
//lock (GlobalData. RealAlarmVariables)
if (item.EventType == EventTypeEnum.Alarm)
{
// 从实时报警列表中移除旧的报警信息,并添加新的报警信息
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
// 如果是触发报警事件
//lock (GlobalData. RealAlarmVariables)
{
// 从实时报警列表中移除旧的报警信息,并添加新的报警信息
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
}
else if (item.EventType == EventTypeEnum.Finish)
{
// 如果是需恢复报警事件,则从实时报警列表中移除该变量
if (item.AlarmConfirm)
{
GlobalData.RealAlarmIdVariables.TryRemove(item.Id, out _);
item.EventType = EventTypeEnum.ConfirmAndFinish;
}
else
{
GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
}
GlobalData.AlarmChange(item.AdaptAlarmVariable());
}
else if (item.EventType == EventTypeEnum.Finish)
{
// 如果是需恢复报警事件,则从实时报警列表中移除该变量
GlobalData.RealAlarmIdVariables.TryRemove(item.Id, out _);
//GlobalData.RealAlarmIdVariables.AddOrUpdate(item.Id, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
GlobalData.AlarmChange(item.AdaptAlarmVariable());
}
}
public void ConfirmAlarm(long variableId)
{
// 如果是确认报警事件
if (GlobalData.AlarmEnableIdVariables.TryGetValue(variableId, out var variableRuntime))
if (GlobalData.AlarmEnableIdVariables.TryGetValue(variableId, out var item))
{
variableRuntime.EventType = EventTypeEnum.Confirm;
variableRuntime.EventTime = DateTime.Now;
GlobalData.RealAlarmIdVariables.AddOrUpdate(variableId, a => variableRuntime.AdaptAlarmVariable(), (a, b) => variableRuntime.AdaptAlarmVariable());
GlobalData.AlarmChange(variableRuntime.AdaptAlarmVariable());
lock (item.AlarmLockObject)
{
item.AlarmConfirm = true;
item.EventTime = DateTime.Now;
if (item.EventType == EventTypeEnum.Finish)
{
item.EventType = EventTypeEnum.ConfirmAndFinish;
GlobalData.RealAlarmIdVariables.TryRemove(variableId, out _);
}
else
{
item.EventType = EventTypeEnum.Confirm;
GlobalData.RealAlarmIdVariables.AddOrUpdate(variableId, a => item.AdaptAlarmVariable(), (a, b) => item.AdaptAlarmVariable());
}
GlobalData.AlarmChange(item.AdaptAlarmVariable());
}
}
}

View File

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

View File

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

View File

@@ -8,20 +8,16 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.DB;
namespace ThingsGateway.Upgrade;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 关系表种子数据
/// 设备采集报警后台服务
/// </summary>
public class SysRelationSeedData : ISqlSugarEntitySeedData<SysRelation>
internal sealed class RealAlarmService : IRealAlarmService
{
/// <inheritdoc/>
public IEnumerable<SysRelation> SeedData()
public Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariables()
{
var data = SeedDataUtil.GetSeedData<SysRelation>(PathExtensions.CombinePathWithOs("SeedData", "Upgrade", "seed_upgrade_relation.json"));
var assembly = GetType().Assembly;
return SeedDataUtil.GetSeedDataByJson<SysRelation>(SeedDataUtil.GetManifestResourceStream(assembly, "SeedData.Upgrade.seed_upgrade_relation.json")).Concat(data);
return GlobalData.GetCurrentUserRealAlarmVariables();
}
}

View File

@@ -259,12 +259,12 @@ public class ChannelRuntimeService : IChannelRuntimeService
try
{
await WaitLock.WaitAsync().ConfigureAwait(false);
var channelIds = GlobalData.IdChannels.Keys.ToList();
//网关启动时,获取所有通道
var newChannelRuntimes = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id) || !GlobalData.IdChannels.ContainsKey(a.Id)).AdaptListChannelRuntime();
var newChannelRuntimes = (await GlobalData.ChannelService.GetFromDBAsync((a => ids.Contains(a.Id) || !channelIds.Contains(a.Id))).ConfigureAwait(false)).AdaptListChannelRuntime();
var chanelIds = newChannelRuntimes.Select(a => a.Id).ToHashSet();
var newDeviceRuntimes = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).Where(a => chanelIds.Contains(a.ChannelId)).AdaptListDeviceRuntime();
var newDeviceRuntimes = (await GlobalData.DeviceService.GetFromDBAsync((a => chanelIds.Contains(a.ChannelId))).ConfigureAwait(false)).AdaptListDeviceRuntime();
await RuntimeServiceHelper.InitAsync(newChannelRuntimes, newDeviceRuntimes, _logger).ConfigureAwait(false);

View File

@@ -16,6 +16,7 @@ using MiniExcelLibs;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
@@ -225,23 +226,20 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
/// <inheritdoc />
public void DeleteChannelFromCache()
{
App.CacheService.Remove(ThingsGatewayCacheConst.Cache_Channel);//删除通道缓存
//App.CacheService.Remove(ThingsGatewayCacheConst.Cache_Channel);//删除通道缓存
}
/// <summary>
/// 从缓存/数据库获取全部信息
/// </summary>
/// <returns>列表</returns>
public async Task<List<Channel>> GetAllAsync(SqlSugarClient db = null)
public async Task<List<Channel>> GetFromDBAsync(Expression<Func<Channel, bool>> expression = null, SqlSugarClient db = null)
{
var key = ThingsGatewayCacheConst.Cache_Channel;
var channels = App.CacheService.Get<List<Channel>>(key);
if (channels == null)
{
db ??= GetDB();
channels = await db.Queryable<Channel>().OrderBy(a => a.Id).ToListAsync().ConfigureAwait(false);
App.CacheService.Set(key, channels);
}
db ??= GetDB();
var channels = await db.Queryable<Channel>().WhereIF(expression != null, expression).OrderBy(a => a.Id).ToListAsync().ConfigureAwait(false);
return channels;
}
@@ -262,8 +260,8 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
HashSet<long>? channel = null;
if (exportFilter.PluginType != null)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = (await GetAllAsync().ConfigureAwait(false)).Where(a => pluginInfo.Contains(a.PluginName)).Select(a => a.Id).ToHashSet();
var pluginInfo = GlobalData.PluginService.GetPluginListSync(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = GlobalData.IdChannels.Where(a => pluginInfo.Contains(a.Value.PluginName)).Select(a => a.Value.Id).ToHashSet();
}
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
var whereQuery = (ISugarQueryable<Channel> a) => a
@@ -285,7 +283,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
[OperDesc("SaveChannel", localizerType: typeof(Channel))]
public async Task<bool> SaveChannelAsync(Channel input, ItemChangedType type)
{
if ((await GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name).TryGetValue(input.Name, out var channel))
if (GlobalData.Channels.TryGetValue(input.Name, out var channel))
{
if (channel.Id != input.Id)
{
@@ -409,7 +407,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
var sheetNames = MiniExcel.GetSheetNames(path);
var channelDicts = (await GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
var channelDicts = GlobalData.Channels;
//导入检验结果
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
//设备页
@@ -429,7 +427,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
}
}
public void SetChannelData(HashSet<long>? dataScope, Dictionary<string, Channel> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, string sheetName, IEnumerable<IDictionary<string, object>> rows)
public void SetChannelData(HashSet<long>? dataScope, IReadOnlyDictionary<string, ChannelRuntime> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, string sheetName, IEnumerable<IDictionary<string, object>> rows)
{
#region sheet

View File

@@ -127,7 +127,7 @@ public static class ChannelServiceHelpers
public static async Task<Dictionary<string, ImportPreviewOutputBase>> ImportAsync(USheetDatas uSheetDatas)
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
var channelDicts = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
var channelDicts = GlobalData.Channels;
//导入检验结果
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
//设备页

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