Compare commits

..

21 Commits

Author SHA1 Message Date
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
Diego
fb1699ce80 10.9.97 2025-07-31 14:06:47 +08:00
Diego
44adddbcd4 10.9.97 2025-07-31 13:56:49 +08:00
Diego
0eab889452 10.9.97 2025-07-31 13:56:34 +08:00
Diego
e14d39a459 添加 rpc写入 多写日志 2025-07-31 12:52:40 +08:00
2248356998 qq.com
7575264ede 添加变量初始化标记 2025-07-31 00:40:03 +08:00
2248356998 qq.com
3e1a077b96 添加变量初始化标记 2025-07-31 00:30:53 +08:00
2248356998 qq.com
a921cb8400 refactor: 连接时判断setup 2025-07-30 23:27:35 +08:00
331 changed files with 4149 additions and 5592 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.8.2" />
<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

@@ -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);
}
@@ -68,34 +68,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 +115,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 +128,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 +171,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");
@@ -184,7 +185,7 @@ namespace ThingsGateway.SqlSugar
// 发送请求并处理响应
var httpResponseMessage =
await Post(client, name, httpContent).ConfigureAwait(false);
await Post(client, tableName, httpContent).ConfigureAwait(false);
var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync);
@@ -266,11 +267,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 +282,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

@@ -43,7 +43,7 @@ namespace ThingsGateway.SqlSugar
}
public class ReflectionInoCore<V>
{
private MemoryCache InstanceCache => MemoryCache.Instance;
private MemoryCache InstanceCache => new MemoryCache() { Expire = 60 };
private static ReflectionInoCore<V> _instance = null;
private static readonly object _instanceLock = new object();
private ReflectionInoCore() { }
@@ -86,7 +86,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)
@@ -109,7 +109,8 @@ namespace ThingsGateway.SqlSugar
public V GetOrCreate(string cacheKey, Func<V> create)
{
return InstanceCache.GetOrAdd<V>(cacheKey, (a) => create());
return InstanceCache.GetOrAdd<V>(cacheKey, (a) =>
create());
}
}
public static class ReflectionInoHelper

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

@@ -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.91</PluginVersion>
<ProPluginVersion>10.9.91</ProPluginVersion>
<DefaultVersion>10.9.92</DefaultVersion>
<AuthenticationVersion>2.9.29</AuthenticationVersion>
<SourceGeneratorVersion>10.9.29</SourceGeneratorVersion>
<NET8Version>8.0.18</NET8Version>
<NET9Version>9.0.7</NET9Version>
<PluginVersion>10.10.6</PluginVersion>
<ProPluginVersion>10.10.6</ProPluginVersion>
<DefaultVersion>10.10.9</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

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.10.0" />
<PackageReference Include="CS-Script" Version="4.10.1" />
</ItemGroup>
<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; }
@@ -394,6 +394,8 @@ public abstract class DeviceBase : DisposableObject, IDevice
await connectWaitLock.WaitAsync(token).ConfigureAwait(false);
if (AutoConnect && Channel != null && Channel?.Online != true)
{
if (Channel.PluginManager == null)
await Channel.SetupAsync(Channel.Config.Clone()).ConfigureAwait(false);
await Channel.CloseAsync().ConfigureAwait(false);
await Task.Delay(500, token).ConfigureAwait(false);
await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, token).ConfigureAwait(false);
@@ -444,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)
{
@@ -462,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/>
@@ -540,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);
@@ -562,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)
{
@@ -575,25 +594,11 @@ public abstract class DeviceBase : DisposableObject, IDevice
}
else
{
if (cancellationToken.IsCancellationRequested)
{
if (!this.DisposedValue)
{
await Task.Delay(timeout, CancellationToken.None).ConfigureAwait(false);
}
}
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
@@ -1029,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)
{
@@ -1046,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

@@ -94,7 +94,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
IdVariableRuntimes.ForEach(a =>
{
if (((!_businessPropertyWithCacheInterval.OnlineFilter) || a.Value.IsOnline) && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
VariableValueInit(a.Value);
});
}
}
@@ -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>
@@ -269,7 +269,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
IdVariableRuntimes.ForEach(a =>
{
if (((!_businessPropertyWithCacheInterval.OnlineFilter) || a.Value.IsOnline) && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.AdaptVariableBasicData());
VariableValueInit(a.Value);
});
}
}
@@ -316,4 +316,29 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
VariableChange(variableRuntime, variable);
}
}
/// <summary>
/// 当初始化时触发此事件处理方法。该方法内部会检查是否需要进行变量上传,如果需要,则调用 <see cref="VariableChange(VariableRuntime, VariableBasicData)"/> 方法。
/// </summary>
/// <param name="variableRuntime">变量运行时信息</param>
protected void VariableValueInit(VariableRuntime variableRuntime)
{
if (CurrentDevice?.Pause != false)
return;
if (!VarModelEnable) return;
if (TaskSchedulerLoop?.Stoped == true) return;
// 如果业务属性的缓存为间隔上传,则不执行后续操作
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
// 检查当前设备的变量是否包含此变量,如果包含,则触发变量的变化处理方法
if (IdVariableRuntimes.ContainsKey(variableRuntime.Id))
{
var data = variableRuntime.AdaptVariableBasicData();
data.ValueInited = false;
VariableChange(variableRuntime, data);
}
}
}
}

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();
@@ -188,8 +189,6 @@ public abstract class CollectFoundationBase : CollectBase
{
try
{
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
LogMessage?.Debug(string.Format("{0} - Writing [{1} - {2} - {3}]", DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType));
// 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
var result = await FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false);

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,6 +17,7 @@ using TouchSocket.Core;
using TouchSocket.Sockets;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 通道表
@@ -32,6 +33,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,6 +17,7 @@ using System.ComponentModel.DataAnnotations;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 设备表

View File

@@ -16,6 +16,7 @@ using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 设备变量表
@@ -31,6 +32,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>

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,4 +1,22 @@
{
"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": {
@@ -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",
@@ -295,7 +309,8 @@
"RetryCount": "RetryCount"
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"RetryCount": "RetryCount"
"RetryCount": "RetryCount",
"DutyCycle": "DutyCycle"
},
"ThingsGateway.Gateway.Application.ControlController": {
"BatchSaveChannelAsync": "BatchSaveChannel",

View File

@@ -1,4 +1,22 @@
{
"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": {
@@ -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版本",
@@ -294,7 +309,8 @@
"RetryCount": "失败重试次数"
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"RetryCount": "失败重试次数"
"RetryCount": "失败重试次数",
"DutyCycle": "占空比"
},
"ThingsGateway.Gateway.Application.ControlController": {
"BatchSaveChannelAsync": "保存通道",

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

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

@@ -188,4 +188,7 @@ public class VariableBasicData
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]
public long CreateOrgId { get; set; }
/// <inheritdoc cref="VariableRuntime.ValueInited"/>
public bool ValueInited { get; set; }
}

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

@@ -36,6 +36,7 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
private bool _isOnline;
private bool _isOnlineChanged;
private bool _valueInited;
private string alarmLimit;
private string alarmText;
@@ -165,6 +166,8 @@ public partial class VariableRuntime : Variable, IVariable, IDisposable
LastSetValue = _value;
ValueInited = true;
if (_isOnline == true)
{
_value = data;

View File

@@ -20,6 +20,8 @@ namespace ThingsGateway.Gateway.Application;
public partial class VariableRuntime : Variable, IVariable, IDisposable
{
[AutoGenerateColumn(Visible = false)]
public bool ValueInited { get => _valueInited; set => _valueInited = value; }
#region
/// <summary>

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

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

@@ -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();
//设备页

View File

@@ -12,6 +12,8 @@ using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Forms;
using System.Linq.Expressions;
namespace ThingsGateway.Gateway.Application;
/// <summary>
@@ -57,7 +59,7 @@ internal interface IChannelService
/// 从缓存/数据库获取全部信息
/// </summary>
/// <returns>通道列表</returns>
Task<List<Channel>> GetAllAsync(SqlSugarClient db = null);
Task<List<Channel>> GetFromDBAsync(Expression<Func<Channel, bool>> expression = null, SqlSugarClient db = null);
/// <summary>
/// 导入通道数据
@@ -92,7 +94,7 @@ internal interface IChannelService
/// <param name="type">保存类型</param>
Task<bool> BatchSaveAsync(List<Channel> input, ItemChangedType type);
void SetChannelData(HashSet<long>? dataScope, Dictionary<string, Channel> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, string sheetName, IEnumerable<IDictionary<string, object>> rows);
void SetChannelData(HashSet<long>? dataScope, IReadOnlyDictionary<string, ChannelRuntime> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, string sheetName, IEnumerable<IDictionary<string, object>> rows);
/// <summary>
/// 保存是否输出日志和日志等级

View File

@@ -16,6 +16,7 @@ using MiniExcelLibs;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
@@ -129,11 +130,11 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
//事务
var result = await db.UseTranAsync(async () =>
{
var data = (await GetAllAsync(db).ConfigureAwait(false))
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
.Where(a => IdhashSet.Contains(a.ChannelId))
.Select(a => a.Id).ToList();
var data = GlobalData.IdDevices
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId)
.Where(a => IdhashSet.Contains(a.Value.ChannelId))
.Select(a => a.Value.Id).ToList();
await db.Deleteable<Device>(a => data.Contains(a.Id)).ExecuteCommandAsync().ConfigureAwait(false);
await variableService.DeleteByDeviceIdAsync(data, db).ConfigureAwait(false);
}).ConfigureAwait(false);
@@ -180,30 +181,19 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
/// <inheritdoc />
public void DeleteDeviceFromCache()
{
App.CacheService.Remove(ThingsGatewayCacheConst.Cache_Device);//删除设备缓存
//App.CacheService.Remove(ThingsGatewayCacheConst.Cache_Device);//删除设备缓存
}
/// <summary>
/// 从缓存/数据库获取全部信息
/// </summary>
/// <returns>列表</returns>
public async Task<List<Device>> GetAllAsync(SqlSugarClient db = null)
public async Task<List<Device>> GetFromDBAsync(Expression<Func<Device, bool>> expression = null, SqlSugarClient db = null)
{
var key = ThingsGatewayCacheConst.Cache_Device;
var devices = App.CacheService.Get<List<Device>>(key);
if (devices == null)
{
db ??= GetDB();
devices = await db.Queryable<Device>().OrderBy(a => a.Id).ToListAsync().ConfigureAwait(false);
App.CacheService.Set(key, devices);
}
return devices;
}
db ??= GetDB();
var devices = await db.Queryable<Device>().WhereIF(expression != null, expression).OrderBy(a => a.Id).ToListAsync().ConfigureAwait(false);
public async Task<Device?> GetDeviceByIdAsync(long id)
{
var data = await GetAllAsync().ConfigureAwait(false);
return data?.FirstOrDefault(x => x.Id == id);
return devices;
}
/// <summary>
@@ -222,12 +212,12 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
HashSet<long>? channel = null;
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
{
channel = (await _channelService.GetAllAsync().ConfigureAwait(false)).Where(a => a.PluginName == exportFilter.PluginName).Select(a => a.Id).ToHashSet();
channel = (GlobalData.IdChannels).Where(a => a.Value.PluginName == exportFilter.PluginName).Select(a => a.Value.Id).ToHashSet();
}
if (exportFilter.PluginType != null)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = (await _channelService.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<Device> a) => a
@@ -244,12 +234,12 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
HashSet<long>? channel = null;
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
{
channel = (await _channelService.GetAllAsync().ConfigureAwait(false)).Where(a => a.PluginName == exportFilter.PluginName).Select(a => a.Id).ToHashSet();
channel = (GlobalData.IdChannels).Where(a => a.Value.PluginName == exportFilter.PluginName).Select(a => a.Value.Id).ToHashSet();
}
if (exportFilter.PluginType != null)
{
var pluginInfo = GlobalData.PluginService.GetList(exportFilter.PluginType).Select(a => a.FullName).ToHashSet();
channel = (await _channelService.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 = (IEnumerable<Device> a) => a
@@ -269,7 +259,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
[OperDesc("SaveDevice", localizerType: typeof(Device))]
public async Task<bool> SaveDeviceAsync(Device input, ItemChangedType type)
{
if ((await GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name).TryGetValue(input.Name, out var device))
if (GlobalData.Devices.TryGetValue(input.Name, out var device))
{
if (device.Id != input.Id)
{
@@ -318,8 +308,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
var devices = await GetAsyncEnumerableData(exportFilter).ConfigureAwait(false);
var plugins = await GetAsyncEnumerableData(exportFilter).ConfigureAwait(false);
var devicesSql = await GetEnumerableData(exportFilter).ConfigureAwait(false);
var deviceDicts = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Id);
var channelDicts = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Id);
var deviceDicts = GlobalData.IdDevices;
var channelDicts = GlobalData.IdChannels;
var pluginSheetNames = (await devicesSql.Select(a => a.ChannelId).ToListAsync().ConfigureAwait(false)).Select(a =>
{
channelDicts.TryGetValue(a, out var channel);
@@ -350,8 +340,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
[OperDesc("ExportDevice", isRecordPar: false, localizerType: typeof(Device))]
public async Task<MemoryStream> ExportMemoryStream(List<Device>? models, string channelName = null, string plugin = null)
{
var deviceDicts = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Id);
var channelDicts = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Id);
var deviceDicts = GlobalData.IdDevices;
var channelDicts = GlobalData.IdChannels;
var pluginSheetNames = models.Select(a => a.ChannelId).Select(a =>
{
channelDicts.TryGetValue(a, out var channel);
@@ -415,10 +405,10 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
var sheetNames = MiniExcel.GetSheetNames(path);
// 获取所有设备,并将设备名称作为键构建设备字典
var deviceDicts = (await GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
var deviceDicts = GlobalData.Devices;
// 获取所有通道,并将通道名称作为键构建通道字典
var channelDicts = (await _channelService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
var channelDicts = GlobalData.Channels;
// 导入检验结果的预览字典,键为名称,值为导入预览对象
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
@@ -427,7 +417,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
ImportPreviewOutput<Device> deviceImportPreview = new();
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
var driverPluginNameDict = _pluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
var driverPluginNameDict = _pluginService.GetPluginListSync().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
foreach (var sheetName in sheetNames)
{
@@ -446,7 +436,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
}
}
public void SetDeviceData(HashSet<long>? dataScope, Dictionary<string, Device> deviceDicts, Dictionary<string, Channel> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ref ImportPreviewOutput<Device> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
public void SetDeviceData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, IReadOnlyDictionary<string, ChannelRuntime> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ref ImportPreviewOutput<Device> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
{
#region sheet

View File

@@ -19,10 +19,10 @@ namespace ThingsGateway.Gateway.Application;
public static class DeviceServiceHelpers
{
public static async Task<USheetDatas> ExportDeviceAsync(IEnumerable<Device> models)
public static USheetDatas ExportDevice(IEnumerable<Device> models)
{
var deviceDicts = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Id);
var channelDicts = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Id);
var deviceDicts = GlobalData.IdDevices;
var channelDicts = GlobalData.IdChannels;
var pluginSheetNames = models.Select(a => a.ChannelId).Select(a =>
{
channelDicts.TryGetValue(a, out var channel);
@@ -35,8 +35,8 @@ public static class DeviceServiceHelpers
public static Dictionary<string, object> ExportSheets(
IEnumerable<Device>? data,
Dictionary<long, Device>? deviceDicts,
Dictionary<long, Channel> channelDicts,
IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts,
IReadOnlyDictionary<long, ChannelRuntime> channelDicts,
HashSet<string> pluginSheetNames,
string? channelName = null)
{
@@ -50,7 +50,7 @@ HashSet<string> pluginSheetNames,
foreach (var plugin in pluginSheetNames)
{
var filtered = FilterPluginDevices(data, plugin, channelDicts);
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin);
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
result.Add(filtResult.TypeName, pluginSheets);
}
@@ -60,8 +60,8 @@ HashSet<string> pluginSheetNames,
public static Dictionary<string, object> ExportSheets(
IAsyncEnumerable<Device>? data1,
IAsyncEnumerable<Device>? data2,
Dictionary<long, Device>? deviceDicts,
Dictionary<long, Channel> channelDicts,
IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts,
IReadOnlyDictionary<long, ChannelRuntime> channelDicts,
HashSet<string> pluginSheetNames,
string? channelName = null)
{
@@ -75,14 +75,14 @@ string? channelName = null)
foreach (var plugin in pluginSheetNames)
{
var filtered = FilterPluginDevices(data2, plugin, channelDicts);
var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
var filtResult = PluginInfoUtil.GetFileNameAndTypeName(plugin);
var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
result.Add(filtResult.TypeName, pluginSheets);
}
return result;
}
static IAsyncEnumerable<Device> FilterPluginDevices(IAsyncEnumerable<Device> data, string plugin, Dictionary<long, Channel> channelDicts)
static IAsyncEnumerable<Device> FilterPluginDevices(IAsyncEnumerable<Device> data, string plugin, IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
{
return data.Where(device =>
{
@@ -99,7 +99,7 @@ string? channelName = null)
}
});
}
static IEnumerable<Device> FilterPluginDevices(IEnumerable<Device> data, string plugin, Dictionary<long, Channel> channelDicts)
static IEnumerable<Device> FilterPluginDevices(IEnumerable<Device> data, string plugin, IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
{
return data.Where(device =>
{
@@ -119,8 +119,8 @@ string? channelName = null)
static IEnumerable<Dictionary<string, object>> GetDeviceSheets(
IEnumerable<Device> data,
Dictionary<long, Device>? deviceDicts,
Dictionary<long, Channel> channelDicts,
IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts,
IReadOnlyDictionary<long, ChannelRuntime> channelDicts,
string? channelName)
{
var type = typeof(Device);
@@ -157,8 +157,8 @@ Dictionary<long, Device>? deviceDicts,
static async IAsyncEnumerable<Dictionary<string, object>> GetDeviceSheets(
IAsyncEnumerable<Device> data,
Dictionary<long, Device>? deviceDicts,
Dictionary<long, Channel> channelDicts,
IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts,
IReadOnlyDictionary<long, ChannelRuntime> channelDicts,
string? channelName)
{
var type = typeof(Device);
@@ -201,8 +201,8 @@ Dictionary<long, Device>? deviceDicts,
Device device,
IEnumerable<PropertyInfo>? propertyInfos,
Type type,
Dictionary<long, Device>? deviceDicts,
Dictionary<long, Channel>? channelDicts,
IReadOnlyDictionary<long, DeviceRuntime>? deviceDicts,
IReadOnlyDictionary<long, ChannelRuntime>? channelDicts,
string? channelName)
{
Dictionary<string, object> devExport = new();
@@ -276,10 +276,10 @@ string? channelName)
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
// 获取所有设备,并将设备名称作为键构建设备字典
var deviceDicts = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
var deviceDicts = GlobalData.Devices;
// 获取所有通道,并将通道名称作为键构建通道字典
var channelDicts = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
var channelDicts = GlobalData.Channels;
// 导入检验结果的预览字典,键为名称,值为导入预览对象
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
@@ -288,10 +288,10 @@ string? channelName)
ImportPreviewOutput<Device> deviceImportPreview = new();
// 获取所有驱动程序,并将驱动程序的完整名称作为键构建字典
var driverPluginFullNameDict = GlobalData.PluginService.GetList().ToDictionary(a => a.FullName);
var driverPluginFullNameDict = GlobalData.PluginService.GetPluginListSync().ToDictionary(a => a.FullName);
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
var driverPluginNameDict = GlobalData.PluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
var driverPluginNameDict = GlobalData.PluginService.GetPluginListSync().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
var sheetNames = uSheetDatas.sheets.Keys.ToList();

View File

@@ -13,6 +13,7 @@ using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Forms;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
namespace ThingsGateway.Gateway.Application;
@@ -73,14 +74,7 @@ internal interface IDeviceService
/// 获取所有设备信息。
/// </summary>
/// <returns>所有设备信息</returns>
Task<List<Device>> GetAllAsync(SqlSugarClient db = null);
/// <summary>
/// 根据ID获取设备信息。
/// </summary>
/// <param name="id">设备ID</param>
/// <returns>设备信息</returns>
Task<Device?> GetDeviceByIdAsync(long id);
Task<List<Device>> GetFromDBAsync(Expression<Func<Device, bool>> expression = null, SqlSugarClient db = null);
/// <summary>
/// 导入设备信息。
@@ -119,7 +113,7 @@ internal interface IDeviceService
/// <returns>保存是否成功的异步任务</returns>
Task<bool> BatchSaveDeviceAsync(List<Device> input, ItemChangedType type);
void SetDeviceData(HashSet<long>? dataScope, Dictionary<string, Device> deviceDicts, Dictionary<string, Channel> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ref ImportPreviewOutput<Device> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows);
void SetDeviceData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, IReadOnlyDictionary<string, ChannelRuntime> channelDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ref ImportPreviewOutput<Device> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows);
/// <summary>
/// 保存是否输出日志和日志等级

View File

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

View File

@@ -26,19 +26,19 @@ public interface IBackendLogService
/// 获取最新的十条 BackendLog 记录
/// </summary>
/// <returns>最新的十条记录</returns>
Task<List<BackendLog>> GetNewLog();
Task<List<BackendLog>> GetNewBackendLog();
/// <summary>
/// 分页查询 BackendLog 数据
/// </summary>
/// <param name="option">查询选项</param>
/// <returns>查询到的数据</returns>
Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option);
Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option);
/// <summary>
/// 获取最近一段时间内每天的后端日志统计信息
/// </summary>
/// <param name="day">要统计的天数</param>
/// <returns>按天统计的后端日志信息列表</returns>
Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day);
Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day);
}

View File

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

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