Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34000d8d7d | ||
|
|
e785f6660c | ||
|
|
831c611797 | ||
|
|
453817ef86 | ||
|
|
8ce0b981c1 | ||
|
|
4e5c51b54c | ||
|
|
3cc9d31f28 | ||
|
|
10391f869b | ||
|
|
fba0723a6d | ||
|
|
2db3f78f0c | ||
|
|
badf61fe01 | ||
|
|
d74e0952dc | ||
|
|
fb1699ce80 | ||
|
|
44adddbcd4 | ||
|
|
0eab889452 |
@@ -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>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
namespace ThingsGateway.Admin.Application;
|
||||
|
||||
public class USheetDatas
|
||||
{
|
||||
@@ -18,6 +18,7 @@ public class SessionOutput : PrimaryIdEntity
|
||||
/// <summary>
|
||||
/// 主键Id
|
||||
/// </summary>
|
||||
[System.ComponentModel.DataAnnotations.Key]
|
||||
public override long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
{
|
||||
@@ -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'">
|
||||
|
||||
@@ -45,11 +45,11 @@ public class Startup : AppStartup
|
||||
options.ServicesStopConcurrently = true;
|
||||
});
|
||||
|
||||
//// 事件总线
|
||||
//services.AddEventBus(options =>
|
||||
//{
|
||||
// 事件总线
|
||||
services.AddEventBus(options =>
|
||||
{
|
||||
|
||||
//});
|
||||
});
|
||||
|
||||
// 任务调度
|
||||
services.AddSchedule(options => options.AddPersistence<JobPersistence>());
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
namespace ThingsGateway.Common;
|
||||
|
||||
public class SmartTriggerScheduler
|
||||
{
|
||||
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
namespace ThingsGateway.Common;
|
||||
|
||||
public sealed class StringOrdinalIgnoreCaseEqualityComparer : EqualityComparer<string>
|
||||
{
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace ThingsGateway;
|
||||
/// </summary>
|
||||
public sealed class AppSettingsOptions : IConfigurableOptions<AppSettingsOptions>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用规范化文档
|
||||
/// </summary>
|
||||
|
||||
341
src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
Normal file
341
src/Admin/ThingsGateway.Furion/App/Options/MiniRunOptions.cs
Normal 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; }
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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温度获取,Buildroot,CPU温度和主板温度
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
@namespace ThingsGateway.Razor
|
||||
|
||||
@if (show)
|
||||
{
|
||||
<Spinner class="ms-auto"></Spinner>
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
namespace ThingsGateway.Razor;
|
||||
|
||||
public partial class SpinnerComponent
|
||||
{
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.9.92</PluginVersion>
|
||||
<ProPluginVersion>10.9.92</ProPluginVersion>
|
||||
<DefaultVersion>10.9.96</DefaultVersion>
|
||||
<AuthenticationVersion>2.9.29</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.9.29</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.18</NET8Version>
|
||||
<NET9Version>9.0.7</NET9Version>
|
||||
<PluginVersion>10.10.4</PluginVersion>
|
||||
<ProPluginVersion>10.10.4</ProPluginVersion>
|
||||
<DefaultVersion>10.10.7</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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks>
|
||||
<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
/// 等待池
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace ThingsGateway.Foundation;
|
||||
/// <summary>
|
||||
/// 协议基类
|
||||
/// </summary>
|
||||
public abstract class DeviceBase : DisposableObject, IDevice
|
||||
public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IChannel Channel { get; private set; }
|
||||
@@ -446,7 +446,13 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
public virtual OperResult<IClientChannel> GetChannel(string socketId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(socketId))
|
||||
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
|
||||
{
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
return new OperResult<IClientChannel>() { Content = clientChannel };
|
||||
else
|
||||
return new OperResult<IClientChannel>("The communication link cannot be obtained, DtuId must be set!");
|
||||
}
|
||||
|
||||
|
||||
if (Channel is ITcpServiceChannel serviceChannel)
|
||||
{
|
||||
@@ -464,7 +470,12 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
}
|
||||
else
|
||||
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
|
||||
{
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
return new OperResult<IClientChannel>() { Content = clientChannel };
|
||||
else
|
||||
return new OperResult<IClientChannel>("The communication link cannot be obtained!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -542,11 +553,11 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
WaitLock? waitLock = null;
|
||||
try
|
||||
{
|
||||
await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null;
|
||||
waitLock = GetWaitLock(clientChannel, dtuId);
|
||||
|
||||
await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
EndPoint? endPoint = GetUdpEndpoint(dtuId);
|
||||
@@ -564,12 +575,18 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
|
||||
var sendOperResult = await SendAsync(command, clientChannel, endPoint, cancellationToken).ConfigureAwait(false);
|
||||
if (!sendOperResult.IsSuccess)
|
||||
throw sendOperResult.Exception ?? new(sendOperResult.ErrorMessage ?? "unknown error");
|
||||
return new MessageBase(sendOperResult);
|
||||
|
||||
waitData.SetCancellationToken(cancellationToken);
|
||||
|
||||
await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
waitData.SetCancellationToken(Channel.ClosedToken);
|
||||
|
||||
await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
var result = waitData.Check();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
@@ -577,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
|
||||
@@ -1031,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)
|
||||
{
|
||||
@@ -1048,4 +1101,5 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
return a => { };
|
||||
}
|
||||
public abstract ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default);
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ThingsGateway.Foundation;
|
||||
/// <summary>
|
||||
/// 协议设备接口
|
||||
/// </summary>
|
||||
public interface IDevice : IDisposable, IDisposableObject
|
||||
public interface IDevice : IDisposable, IDisposableObject, IAsyncDisposable
|
||||
{
|
||||
#region 属性
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
#pragma warning disable CA1851
|
||||
|
||||
public static class PackHelpers
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
/// 前缀
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,7 +141,8 @@ 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)
|
||||
|
||||
@@ -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)"/> 方法。
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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 写入方法
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -17,6 +17,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
#pragma warning disable CS0649
|
||||
|
||||
/// <summary>
|
||||
/// 设备表
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -295,7 +295,8 @@
|
||||
"RetryCount": "RetryCount"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
|
||||
"RetryCount": "RetryCount"
|
||||
"RetryCount": "RetryCount",
|
||||
"DutyCycle": "DutyCycle"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.ControlController": {
|
||||
"BatchSaveChannelAsync": "BatchSaveChannel",
|
||||
|
||||
@@ -294,7 +294,8 @@
|
||||
"RetryCount": "失败重试次数"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
|
||||
"RetryCount": "失败重试次数"
|
||||
"RetryCount": "失败重试次数",
|
||||
"DutyCycle": "占空比"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.ControlController": {
|
||||
"BatchSaveChannelAsync": "保存通道",
|
||||
|
||||
@@ -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 核心实现
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -263,7 +261,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
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();
|
||||
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
|
||||
|
||||
|
||||
@@ -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();
|
||||
//设备页
|
||||
|
||||
@@ -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>
|
||||
/// 保存是否输出日志和日志等级
|
||||
|
||||
@@ -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();
|
||||
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();
|
||||
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();
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -82,7 +82,7 @@ string? channelName = null)
|
||||
|
||||
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();
|
||||
|
||||
@@ -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>
|
||||
/// 保存是否输出日志和日志等级
|
||||
|
||||
@@ -10,9 +10,13 @@
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public interface IBackendLogService
|
||||
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
|
||||
public interface IBackendLogService : IRpcServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 删除 BackendLog 表中的所有记录
|
||||
@@ -20,12 +24,14 @@ public interface IBackendLogService
|
||||
/// <remarks>
|
||||
/// 调用此方法会删除 BackendLog 表中的所有记录。
|
||||
/// </remarks>
|
||||
[DmtpRpc]
|
||||
Task DeleteBackendLogAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取最新的十条 BackendLog 记录
|
||||
/// </summary>
|
||||
/// <returns>最新的十条记录</returns>
|
||||
[DmtpRpc]
|
||||
Task<List<BackendLog>> GetNewLog();
|
||||
|
||||
/// <summary>
|
||||
@@ -33,6 +39,7 @@ public interface IBackendLogService
|
||||
/// </summary>
|
||||
/// <param name="option">查询选项</param>
|
||||
/// <returns>查询到的数据</returns>
|
||||
[DmtpRpc]
|
||||
Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option);
|
||||
|
||||
/// <summary>
|
||||
@@ -40,5 +47,6 @@ public interface IBackendLogService
|
||||
/// </summary>
|
||||
/// <param name="day">要统计的天数</param>
|
||||
/// <returns>按天统计的后端日志信息列表</returns>
|
||||
[DmtpRpc]
|
||||
Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ internal sealed class ChannelThreadManage : IChannelThreadManage
|
||||
{
|
||||
if (!DeviceThreadManages.TryRemove(channelId, out var deviceThreadManage)) return;
|
||||
|
||||
await deviceThreadManage.DisposeAsync().ConfigureAwait(false);
|
||||
await deviceThreadManage.SafeDisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -298,7 +298,11 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
try
|
||||
{
|
||||
driver = CreateDriver(deviceRuntime);
|
||||
|
||||
if (driver == null)
|
||||
{
|
||||
LogMessage?.LogWarning(string.Format(AppResource.InitFail, CurrentChannel.PluginName, driver?.DeviceName));
|
||||
return;
|
||||
}
|
||||
//初始状态
|
||||
deviceRuntime.DeviceStatus = DeviceStatusEnum.Default;
|
||||
|
||||
@@ -326,7 +330,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
{
|
||||
try
|
||||
{
|
||||
await oldCts.CancelAsync().ConfigureAwait(false);
|
||||
await oldCts.SafeCancelAsync().ConfigureAwait(false);
|
||||
oldCts.SafeDispose();
|
||||
}
|
||||
catch
|
||||
@@ -340,7 +344,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
CancellationTokenSources.TryAdd(driver.DeviceId, cts);
|
||||
|
||||
_ = Task.Factory.StartNew((state) => DriverStart(state, token), driver, token, TaskCreationOptions.None, TaskScheduler.Default);
|
||||
}).ConfigureAwait(false);
|
||||
}, App.HostApplicationLifetime.ApplicationStopping).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -393,45 +397,48 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
try
|
||||
{
|
||||
ConcurrentList<VariableRuntime> saveVariableRuntimes = new();
|
||||
deviceIds.ParallelForEach((deviceId) =>
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
// 查找具有指定设备ID的驱动程序对象
|
||||
if (Drivers.TryRemove(deviceId, out var driver))
|
||||
{
|
||||
driver.CurrentDevice.SetDeviceStatus(now, false, "Communication connection has been removed");
|
||||
if (IsCollectChannel == true)
|
||||
{
|
||||
foreach (var a in driver.IdVariableRuntimes)
|
||||
{
|
||||
a.Value.SetValue(a.Value.Value, now, false);
|
||||
a.Value.SetErrorMessage("Communication connection has been removed");
|
||||
if (a.Value.SaveValue && !a.Value.DynamicVariable)
|
||||
{
|
||||
saveVariableRuntimes.Add(a.Value);
|
||||
}
|
||||
}
|
||||
await deviceIds.ParallelForEachAsync(async (deviceId, cancellationToken) =>
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
// 查找具有指定设备ID的驱动程序对象
|
||||
if (Drivers.TryRemove(deviceId, out var driver))
|
||||
{
|
||||
driver.CurrentDevice.SetDeviceStatus(now, false, "Communication connection has been removed");
|
||||
if (IsCollectChannel == true)
|
||||
{
|
||||
foreach (var a in driver.IdVariableRuntimes)
|
||||
{
|
||||
a.Value.SetValue(a.Value.Value, now, false);
|
||||
a.Value.SetErrorMessage("Communication connection has been removed");
|
||||
if (a.Value.SaveValue && !a.Value.DynamicVariable)
|
||||
{
|
||||
saveVariableRuntimes.Add(a.Value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 取消驱动程序的操作
|
||||
if (CancellationTokenSources.TryRemove(deviceId, out var token))
|
||||
{
|
||||
if (token != null)
|
||||
{
|
||||
driver.Stop();
|
||||
token.Cancel();
|
||||
token.Dispose();
|
||||
}
|
||||
}
|
||||
// 取消驱动程序的操作
|
||||
if (CancellationTokenSources.TryRemove(deviceId, out var token))
|
||||
{
|
||||
if (token != null)
|
||||
{
|
||||
await token.SafeCancelAsync().ConfigureAwait(false);
|
||||
token.SafeDispose();
|
||||
if (driver != null)
|
||||
{
|
||||
await driver.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DriverTasks.TryRemove(deviceId, out var task))
|
||||
{
|
||||
task.Stop();
|
||||
}
|
||||
});
|
||||
if (DriverTasks.TryRemove(deviceId, out var task))
|
||||
{
|
||||
task.Stop();
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
|
||||
@@ -581,13 +588,13 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
await RemoveDeviceAsync(deviceRuntime.Id).ConfigureAwait(false);
|
||||
|
||||
//获取主设备
|
||||
var devices = await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false);//获取设备属性
|
||||
var devices = GlobalData.IdDevices;
|
||||
|
||||
if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null)
|
||||
{
|
||||
if (!GlobalData.ReadOnlyIdDevices.TryGetValue(deviceRuntime.RedundantDeviceId ?? 0, out newDeviceRuntime))
|
||||
{
|
||||
var newDev = await GlobalData.DeviceService.GetDeviceByIdAsync(deviceRuntime.RedundantDeviceId ?? 0).ConfigureAwait(false);
|
||||
devices.TryGetValue(deviceRuntime.RedundantDeviceId ?? 0, out var newDev);
|
||||
if (newDev == null)
|
||||
{
|
||||
LogMessage?.LogWarning($"Device with deviceId {deviceRuntime.RedundantDeviceId} not found");
|
||||
@@ -608,7 +615,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
newDeviceRuntime = GlobalData.ReadOnlyIdDevices.FirstOrDefault(a => a.Value.RedundantDeviceId == deviceRuntime.Id).Value;
|
||||
if (newDeviceRuntime == null)
|
||||
{
|
||||
var newDev = devices.FirstOrDefault(a => a.RedundantDeviceId == deviceRuntime.Id);
|
||||
var newDev = devices.FirstOrDefault(a => a.Value.RedundantDeviceId == deviceRuntime.Id).Value;
|
||||
if (newDev == null)
|
||||
{
|
||||
LogMessage?.LogWarning($"Device with redundantDeviceId {deviceRuntime.Id} not found");
|
||||
@@ -796,7 +803,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
Disposed = true;
|
||||
try
|
||||
{
|
||||
await CancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
await CancellationTokenSource.SafeCancelAsync().ConfigureAwait(false);
|
||||
CancellationTokenSource.SafeDispose();
|
||||
GlobalData.DeviceStatusChangeEvent -= GlobalData_DeviceStatusChangeEvent;
|
||||
await NewDeviceLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -39,8 +39,8 @@ internal sealed class GatewayMonitorHostedService : BackgroundService, IGatewayM
|
||||
try
|
||||
{
|
||||
//网关启动时,获取所有通道
|
||||
var channelRuntimes = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).AdaptListChannelRuntime();
|
||||
var deviceRuntimes = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).AdaptListDeviceRuntime();
|
||||
var channelRuntimes = (await GlobalData.ChannelService.GetFromDBAsync().ConfigureAwait(false)).AdaptListChannelRuntime();
|
||||
var deviceRuntimes = (await GlobalData.DeviceService.GetFromDBAsync().ConfigureAwait(false)).AdaptListDeviceRuntime();
|
||||
|
||||
var variableRuntimes = GlobalData.VariableService.GetAllVariableRuntime();
|
||||
|
||||
@@ -78,7 +78,7 @@ internal sealed class GatewayMonitorHostedService : BackgroundService, IGatewayM
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await ChannelThreadManage.DisposeAsync().ConfigureAwait(false);
|
||||
await ChannelThreadManage.SafeDisposeAsync().ConfigureAwait(false);
|
||||
await base.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
|
||||
internal interface IRedundantRpcServer : IRpcServer
|
||||
{
|
||||
[DmtpRpc]
|
||||
Task<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas);
|
||||
|
||||
[DmtpRpc]
|
||||
Task SyncData(List<Channel> channels, List<Device> devices, List<Variable> variables);
|
||||
[DmtpRpc]
|
||||
void UpData(ICallContext callContext, List<DeviceDataWithValue> deviceDatas);
|
||||
}
|
||||
|
||||
//internal static partial class RedundantRpcServerExtensions
|
||||
//{
|
||||
// public static Task<Dictionary<string, Dictionary<string, OperResult<object>>>> RpcAsync<TClient>(this TClient client, System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, string>> deviceDatas, IInvokeOption invokeOption = default)
|
||||
// where TClient : TouchSocket.Rpc.IRpcClient, TouchSocket.Dmtp.Rpc.IDmtpRpcActor
|
||||
// {
|
||||
// return client.InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>($"{nameof(ThingsGateway.Management)}{nameof(ThingsGateway.Management)}{nameof(ThingsGateway.Management.IRedundantRpcServer)}{nameof(ThingsGateway.Management.IRedundantRpcServer.Rpc)}", invokeOption, deviceDatas);
|
||||
// }
|
||||
|
||||
//}
|
||||
@@ -21,6 +21,7 @@ using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Rpc.Generators;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
@@ -95,8 +96,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
foreach (var deviceDataWithValues in deviceRunTimes)
|
||||
{
|
||||
// 将 GlobalData.CollectDevices 和 GlobalData.Variables 同步到从站
|
||||
await item.GetDmtpRpcActor().InvokeAsync(
|
||||
nameof(RedundantRpcServer.UpData), null, waitInvoke, deviceDataWithValues.AdaptListDeviceDataWithValue()).ConfigureAwait(false);
|
||||
await item.GetDmtpRpcActor().UpDataAsync(deviceDataWithValues.AdaptListDeviceDataWithValue(), waitInvoke).ConfigureAwait(false);
|
||||
}
|
||||
LogMessage?.LogTrace($"{item.GetIPPort()} Update StandbyStation data success");
|
||||
}
|
||||
@@ -312,10 +312,10 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
{
|
||||
await StopTaskAsync().ConfigureAwait(false);
|
||||
TextLogger?.TryDispose();
|
||||
scheduledTask?.TryDispose();
|
||||
scheduledTask?.SafeDispose();
|
||||
|
||||
_tcpDmtpService?.TryDispose();
|
||||
_tcpDmtpClient?.TryDispose();
|
||||
_tcpDmtpService?.SafeDispose();
|
||||
_tcpDmtpClient?.SafeDispose();
|
||||
_tcpDmtpService = null;
|
||||
_tcpDmtpClient = null;
|
||||
}
|
||||
@@ -336,10 +336,12 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store => store.RegisterServer(new RedundantRpcServer(this)));
|
||||
a.AddRpcStore(store => store.RegisterServer(typeof(IRedundantRpcServer), new RedundantRpcServer(this)));
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear();
|
||||
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||
@@ -365,10 +367,11 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store => store.RegisterServer(new RedundantRpcServer(this)));
|
||||
a.AddRpcStore(store => store.RegisterServer(typeof(IRedundantRpcServer), new RedundantRpcServer(this)));
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear();
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(redundancy.HeartbeatInterval))
|
||||
@@ -470,7 +473,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
if (variableBatch.Count >= maxBatchSize)
|
||||
{
|
||||
// 发送一批
|
||||
await client.GetDmtpRpcActor().InvokeAsync(nameof(RedundantRpcServer.SyncData), null, invokeOption, channelBatch.ToList(), deviceBatch.ToList(), variableBatch).ConfigureAwait(false);
|
||||
await client.GetDmtpRpcActor().SyncDataAsync(channelBatch.ToList(), deviceBatch.ToList(), variableBatch, invokeOption).ConfigureAwait(false);
|
||||
|
||||
variableBatch.Clear();
|
||||
channelBatch.Remove(channel);
|
||||
@@ -482,7 +485,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
// 发送最后剩余的一批
|
||||
if (variableBatch.Count > 0)
|
||||
{
|
||||
await client.GetDmtpRpcActor().InvokeAsync(nameof(RedundantRpcServer.SyncData), null, invokeOption, channelBatch.ToList(), deviceBatch.ToList(), variableBatch).ConfigureAwait(false);
|
||||
await client.GetDmtpRpcActor().SyncDataAsync(channelBatch.ToList(), deviceBatch.ToList(), variableBatch, invokeOption).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
LogMessage?.LogTrace($"ForcedSync data success");
|
||||
@@ -564,10 +567,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas,
|
||||
DmtpInvokeOption invokeOption)
|
||||
{
|
||||
return await _tcpDmtpClient.GetDmtpRpcActor()
|
||||
.InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(RedundantRpcServer.Rpc), invokeOption, deviceDatas)
|
||||
.ConfigureAwait(false);
|
||||
return await _tcpDmtpClient.GetDmtpRpcActor().RpcAsync(deviceDatas, invokeOption).ConfigureAwait(false);
|
||||
}
|
||||
private async Task InvokeRpcServerAsync(
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas,
|
||||
@@ -606,9 +606,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await client.GetDmtpRpcActor().InvokeTAsync<Dictionary<string, Dictionary<string, OperResult<object>>>>(
|
||||
nameof(RedundantRpcServer.Rpc), invokeOption, variableDatas)
|
||||
.ConfigureAwait(false);
|
||||
var data = await client.GetDmtpRpcActor().RpcAsync(variableDatas, invokeOption).ConfigureAwait(false);
|
||||
|
||||
dataResult.AddRange(data);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
internal sealed partial class RedundantRpcServer : SingletonRpcServer
|
||||
internal sealed partial class RedundantRpcServer : SingletonRpcServer, IRedundantRpcServer
|
||||
{
|
||||
RedundancyTask RedundancyTask;
|
||||
public RedundantRpcServer(RedundancyTask redundancyTask)
|
||||
@@ -25,7 +25,7 @@ internal sealed partial class RedundantRpcServer : SingletonRpcServer
|
||||
RedundancyTask = redundancyTask;
|
||||
}
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
[DmtpRpc]
|
||||
public void UpData(ICallContext callContext, List<DeviceDataWithValue> deviceDatas)
|
||||
{
|
||||
foreach (var deviceData in deviceDatas)
|
||||
@@ -50,7 +50,7 @@ internal sealed partial class RedundantRpcServer : SingletonRpcServer
|
||||
RedundancyTask.LogMessage?.Trace("RpcServer Update data success");
|
||||
}
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
[DmtpRpc]
|
||||
public async Task SyncData(List<Channel> channels, List<Device> devices, List<Variable> variables)
|
||||
{
|
||||
List<Channel> addChannels = new();
|
||||
@@ -146,9 +146,13 @@ internal sealed partial class RedundantRpcServer : SingletonRpcServer
|
||||
RedundancyTask.LogMessage?.LogTrace($"Sync data success");
|
||||
}
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public Task<Dictionary<string, Dictionary<string, IOperResult>>> Rpc(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken)
|
||||
[DmtpRpc]
|
||||
public async Task<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||
{
|
||||
return GlobalData.RpcService.InvokeDeviceMethodAsync($"Redundant[{(callContext.Caller is ITcpSession tcpSession ? tcpSession.GetIPPort() : string.Empty)}]", deviceDatas, cancellationToken);
|
||||
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync($"Redundant[{(callContext.Caller is ITcpSession tcpSession ? tcpSession.GetIPPort() : string.Empty)}]", deviceDatas, callContext.Token).ConfigureAwait(false);
|
||||
|
||||
return data.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => b.Value.GetOperResult()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
internal sealed class RemoteManagementHostedService : BackgroundService
|
||||
{
|
||||
|
||||
public RemoteManagementHostedService(ILoggerFactory loggerFactory)
|
||||
{
|
||||
var clientManagementOptions = App.GetOptions<RemoteClientManagementOptions>();
|
||||
var serverManagementOptions = App.GetOptions<RemoteServerManagementOptions>();
|
||||
RemoteClientManagementTask = new RemoteManagementTask(loggerFactory.CreateLogger(nameof(RemoteClientManagementTask)), clientManagementOptions);
|
||||
RemoteServerManagementTask = new RemoteManagementTask(loggerFactory.CreateLogger(nameof(RemoteServerManagementTask)), serverManagementOptions);
|
||||
}
|
||||
|
||||
private RemoteManagementTask RemoteClientManagementTask;
|
||||
private RemoteManagementTask RemoteServerManagementTask;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
await RemoteClientManagementTask.StartAsync(stoppingToken).ConfigureAwait(false);
|
||||
await RemoteServerManagementTask.StartAsync(stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await RemoteClientManagementTask.DisposeAsync().ConfigureAwait(false);
|
||||
await RemoteServerManagementTask.DisposeAsync().ConfigureAwait(false);
|
||||
await base.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -10,28 +10,34 @@
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
using ThingsGateway.ConfigurableOptions;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
public class RemoteManagementOptions
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool IsServer { get; set; }
|
||||
public virtual bool IsServer { get; }
|
||||
|
||||
[Required]
|
||||
public string ServerUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置用于验证的令牌。
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string VerifyToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置心跳间隔。
|
||||
/// </summary>
|
||||
[MinValue(3000)]
|
||||
public int HeartbeatInterval { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class RemoteClientManagementOptions : RemoteManagementOptions, IConfigurableOptions
|
||||
{
|
||||
public override bool IsServer => false;
|
||||
}
|
||||
public class RemoteServerManagementOptions : RemoteManagementOptions, IConfigurableOptions
|
||||
{
|
||||
public override bool IsServer => true;
|
||||
}
|
||||
|
||||
@@ -21,22 +21,24 @@ namespace ThingsGateway.Management;
|
||||
public partial class RemoteManagementRpcServer : SingletonRpcServer,
|
||||
IBackendLogService
|
||||
{
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
[DmtpRpc]
|
||||
public Task DeleteBackendLogAsync() => App.GetService<IBackendLogService>().DeleteBackendLogAsync();
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
[DmtpRpc]
|
||||
public Task<List<BackendLogDayStatisticsOutput>> StatisticsByDayAsync(int day) => App.GetService<IBackendLogService>().StatisticsByDayAsync(day);
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
[DmtpRpc]
|
||||
public Task<List<BackendLog>> GetNewLog() => App.GetService<IBackendLogService>().GetNewLog();
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
[DmtpRpc]
|
||||
public Task<QueryData<BackendLog>> PageAsync(QueryPageOptions option) => App.GetService<IBackendLogService>().PageAsync(option);
|
||||
|
||||
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public Task<Dictionary<string, Dictionary<string, IOperResult>>> Rpc(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken)
|
||||
[DmtpRpc]
|
||||
public async Task<Dictionary<string, Dictionary<string, OperResult<object>>>> Rpc(ICallContext callContext, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken)
|
||||
{
|
||||
return GlobalData.RpcService.InvokeDeviceMethodAsync($"RemoteManagement[{(callContext.Caller is ITcpSession tcpSession ? tcpSession.GetIPPort() : string.Empty)}]", deviceDatas, cancellationToken);
|
||||
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync($"RemoteManagement[{(callContext.Caller is ITcpSession tcpSession ? tcpSession.GetIPPort() : string.Empty)}]", deviceDatas, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return data.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => b.Value.GetOperResult()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Dmtp;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
@@ -16,18 +20,36 @@ using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
public partial class RemoteManagementTask
|
||||
public partial class RemoteManagementTask : AsyncDisposableObject
|
||||
{
|
||||
internal const string LogPath = $"Logs/{nameof(RemoteManagementTask)}";
|
||||
private ILog LogMessage;
|
||||
private ILogger _logger;
|
||||
private TextFileLogger TextLogger;
|
||||
|
||||
public RemoteManagementTask(ILog log)
|
||||
public RemoteManagementTask(ILogger logger, RemoteManagementOptions remoteManagementOptions)
|
||||
{
|
||||
_logger = logger;
|
||||
TextLogger = TextFileLogger.GetMultipleFileLogger(LogPath);
|
||||
TextLogger.LogLevel = TouchSocket.Core.LogLevel.Trace;
|
||||
var log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
log?.AddLogger(TextLogger);
|
||||
LogMessage = log;
|
||||
_remoteManagementOptions = App.GetOptions<RemoteManagementOptions>();
|
||||
|
||||
_remoteManagementOptions = remoteManagementOptions;
|
||||
|
||||
}
|
||||
|
||||
private void Log_Out(TouchSocket.Core.LogLevel logLevel, object source, string message, Exception exception)
|
||||
{
|
||||
_logger?.Log_Out(logLevel, source, message, exception);
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_remoteManagementOptions.Enable) return;
|
||||
|
||||
if (_remoteManagementOptions.IsServer)
|
||||
{
|
||||
_tcpDmtpService ??= await GetTcpDmtpService().ConfigureAwait(false);
|
||||
@@ -68,14 +90,27 @@ public partial class RemoteManagementTask
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store => store.RegisterServer(new RemoteManagementRpcServer()));
|
||||
a.AddRpcStore(store => store.RegisterServer<RemoteManagementRpcServer>());
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear();
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(_remoteManagementOptions.HeartbeatInterval))
|
||||
.SetMaxFailCount(3);
|
||||
|
||||
a.AddDmtpHandshakedPlugin(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await tcpDmtpClient.ResetIdAsync($"{_remoteManagementOptions.Name}:{GlobalData.HardwareJob.HardwareInfo.UUID}").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await tcpDmtpClient.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await tcpDmtpClient.SetupAsync(config).ConfigureAwait(false);
|
||||
@@ -92,10 +127,11 @@ public partial class RemoteManagementTask
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store => store.RegisterServer(new RemoteManagementRpcServer()));
|
||||
a.AddRpcStore(store => store.RegisterServer<RemoteManagementRpcServer>());
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear();
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpHeartbeat()//使用Dmtp心跳
|
||||
.SetTick(TimeSpan.FromMilliseconds(_remoteManagementOptions.HeartbeatInterval))
|
||||
@@ -124,4 +160,22 @@ public partial class RemoteManagementTask
|
||||
await _tcpDmtpClient.ConnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (_tcpDmtpClient != null)
|
||||
{
|
||||
await _tcpDmtpClient.CloseAsync().ConfigureAwait(false);
|
||||
_tcpDmtpClient.SafeDispose();
|
||||
_tcpDmtpClient = null;
|
||||
}
|
||||
if (_tcpDmtpService != null)
|
||||
{
|
||||
await _tcpDmtpService.ClearAsync().ConfigureAwait(false);
|
||||
_tcpDmtpService.SafeDispose();
|
||||
_tcpDmtpService = null;
|
||||
}
|
||||
await base.DisposeAsync(disposing).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
[GeneratorRpcProxy(GeneratorFlag = GeneratorFlag.ExtensionAsync)]
|
||||
public interface IUpgradeRpcServer : IRpcServer
|
||||
{
|
||||
[DmtpRpc]
|
||||
void Restart();
|
||||
[DmtpRpc]
|
||||
Task Upgrade();
|
||||
}
|
||||
@@ -23,8 +23,8 @@ using TouchSocket.Dmtp;
|
||||
using TouchSocket.Dmtp.FileTransfer;
|
||||
using TouchSocket.Dmtp.Rpc;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Rpc.Generators;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
internal sealed class UpdateZipFileHostedService : BackgroundService, IUpdateZipFileHostedService
|
||||
@@ -136,7 +136,7 @@ internal sealed class UpdateZipFileHostedService : BackgroundService, IUpdateZip
|
||||
Token = tokenSource.Token//配置可取消令箭
|
||||
};
|
||||
|
||||
var updateZipFiles = await TcpDmtpClient.GetDmtpRpcActor().InvokeTAsync<List<UpdateZipFile>>(nameof(GetList), invokeOption, new UpdateZipFileInput()
|
||||
var updateZipFiles = await TcpDmtpClient.GetDmtpRpcActor().GetListAsync(new UpdateZipFileInput()
|
||||
{
|
||||
Version = Assembly.GetEntryAssembly().GetName().Version,
|
||||
DotNetVersion = Environment.Version,
|
||||
@@ -145,7 +145,7 @@ internal sealed class UpdateZipFileHostedService : BackgroundService, IUpdateZip
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "OSX" : "Unknown",
|
||||
Architecture = RuntimeInformation.ProcessArchitecture,
|
||||
AppName = "ThingsGateway"
|
||||
}).ConfigureAwait(false);
|
||||
}, invokeOption).ConfigureAwait(false);
|
||||
|
||||
return updateZipFiles.OrderByDescending(a => a.Version).ToList();
|
||||
}
|
||||
@@ -300,12 +300,14 @@ internal sealed class UpdateZipFileHostedService : BackgroundService, IUpdateZip
|
||||
})
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddRpcStore(store => store.RegisterServer<UpgradeRpcServer>());
|
||||
a.AddLogger(_log);
|
||||
var rpcServer = new UpgradeRpcServer();
|
||||
a.AddRpcStore(store => store.RegisterServer<IUpgradeRpcServer>(rpcServer));
|
||||
a.AddDmtpRouteService();//添加路由策略
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear();
|
||||
a.UseDmtpRpc();
|
||||
a.UseDmtpFileTransfer();//必须添加文件传输插件
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ using TouchSocket.Rpc;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
public partial class UpgradeRpcServer : SingletonRpcServer
|
||||
public partial class UpgradeRpcServer : SingletonRpcServer, IUpgradeRpcServer
|
||||
{
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
[DmtpRpc]
|
||||
public void Restart()
|
||||
{
|
||||
RestartServerHelper.RestartServer();
|
||||
}
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
[DmtpRpc]
|
||||
public async Task Upgrade()
|
||||
{
|
||||
var _updateZipFileService = App.GetService<IUpdateZipFileHostedService>();
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
internal sealed class WebApiHostedService : BackgroundService
|
||||
{
|
||||
|
||||
public WebApiHostedService(ILoggerFactory loggerFactory)
|
||||
{
|
||||
var webApiOptions = App.GetOptions<WebApiOptions>();
|
||||
WebApiTask = new WebApiTask(loggerFactory.CreateLogger(nameof(WebApiTask)), webApiOptions);
|
||||
}
|
||||
|
||||
private WebApiTask WebApiTask;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
await WebApiTask.StartAsync(stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await WebApiTask.DisposeAsync().ConfigureAwait(false);
|
||||
await base.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using ThingsGateway.ConfigurableOptions;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
public class WebApiOptions : IConfigurableOptions
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
|
||||
[Required]
|
||||
public string ServerUri { get; set; }
|
||||
|
||||
[Required]
|
||||
public string UserName { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Password { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Http;
|
||||
using TouchSocket.Rpc;
|
||||
using TouchSocket.Sockets;
|
||||
using TouchSocket.WebApi.Swagger;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
public partial class WebApiTask : AsyncDisposableObject
|
||||
{
|
||||
internal const string LogPath = $"Logs/{nameof(WebApiTask)}";
|
||||
private ILog LogMessage;
|
||||
private ILogger _logger;
|
||||
private TextFileLogger TextLogger;
|
||||
|
||||
public WebApiTask(ILogger logger, WebApiOptions webApiOptions)
|
||||
{
|
||||
_logger = logger;
|
||||
TextLogger = TextFileLogger.GetMultipleFileLogger(LogPath);
|
||||
TextLogger.LogLevel = TouchSocket.Core.LogLevel.Trace;
|
||||
var log = new LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
log?.AddLogger(new EasyLogger(Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
log?.AddLogger(TextLogger);
|
||||
LogMessage = log;
|
||||
|
||||
_webApiOptions = webApiOptions;
|
||||
|
||||
}
|
||||
|
||||
private void Log_Out(TouchSocket.Core.LogLevel logLevel, object source, string message, Exception exception)
|
||||
{
|
||||
_logger?.Log_Out(logLevel, source, message, exception);
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_webApiOptions.Enable) return;
|
||||
|
||||
_httpService ??= await GetHttpService().ConfigureAwait(false);
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await EnsureChannelOpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Start");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(10000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private HttpService? _httpService;
|
||||
private WebApiOptions _webApiOptions;
|
||||
|
||||
|
||||
private async Task<HttpService> GetHttpService()
|
||||
{
|
||||
var httpService = new HttpService();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetListenIPHosts(_webApiOptions.ServerUri)
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddLogger(LogMessage);
|
||||
a.AddRpcStore(store =>
|
||||
{
|
||||
store.RegisterServer<ControlController>();
|
||||
store.RegisterServer<RuntimeInfoController>();
|
||||
});
|
||||
|
||||
//添加跨域服务
|
||||
a.AddCors(corsOption =>
|
||||
{
|
||||
//添加跨域策略,后续使用policyName即可应用跨域策略。
|
||||
corsOption.Add("cors", corsBuilder =>
|
||||
{
|
||||
corsBuilder.AllowAnyMethod()
|
||||
.AllowAnyOrigin();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseTcpSessionCheckClear();
|
||||
|
||||
a.Add(new AuthenticationPlugin(_webApiOptions));
|
||||
|
||||
a.UseWebApi();
|
||||
|
||||
a.UseSwagger();
|
||||
|
||||
a.UseDefaultHttpServicePlugin();
|
||||
});
|
||||
|
||||
await httpService.SetupAsync(config).ConfigureAwait(false);
|
||||
return httpService;
|
||||
}
|
||||
|
||||
private async Task EnsureChannelOpenAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_httpService.ServerState != ServerState.Running)
|
||||
{
|
||||
if (_httpService.ServerState != ServerState.Stopped)
|
||||
await _httpService.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await _httpService.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected override async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
|
||||
if (_httpService != null)
|
||||
{
|
||||
await _httpService.ClearAsync().ConfigureAwait(false);
|
||||
_httpService.SafeDispose();
|
||||
_httpService = null;
|
||||
}
|
||||
await base.DisposeAsync(disposing).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 鉴权插件
|
||||
/// </summary>
|
||||
class AuthenticationPlugin : PluginBase, IHttpPlugin
|
||||
{
|
||||
WebApiOptions _webApiOptions;
|
||||
public AuthenticationPlugin(WebApiOptions webApiOptions)
|
||||
{
|
||||
_webApiOptions = webApiOptions;
|
||||
}
|
||||
public Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e)
|
||||
{
|
||||
string authorizationHeader = e.Context.Request.Headers["Authorization"];
|
||||
if (string.IsNullOrEmpty(authorizationHeader))
|
||||
{
|
||||
|
||||
e.Context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"ThingsGateway\"");
|
||||
return e.Context.Response
|
||||
.SetStatus(401, "Empty Authorization Header")
|
||||
.AnswerAsync();
|
||||
}
|
||||
|
||||
if (!authorizationHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
e.Context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"ThingsGateway\"");
|
||||
return e.Context.Response
|
||||
.SetStatus(401, "Invalid Authorization Header")
|
||||
.AnswerAsync();
|
||||
}
|
||||
|
||||
var authBase64 = authorizationHeader.Substring("Basic ".Length).Trim();
|
||||
var authBytes = Convert.FromBase64String(authBase64);
|
||||
var authString = Encoding.UTF8.GetString(authBytes);
|
||||
var credentials = authString.Split(':', 2);
|
||||
|
||||
if (credentials.Length != 2)
|
||||
{
|
||||
e.Context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"ThingsGateway\"");
|
||||
return e.Context.Response
|
||||
.SetStatus(401, "Invalid Authorization Header")
|
||||
.AnswerAsync();
|
||||
}
|
||||
|
||||
var username = credentials[0];
|
||||
var password = credentials[1];
|
||||
|
||||
// 这里验证用户名和密码,实际项目中应该从数据库验证
|
||||
if (username != _webApiOptions.UserName || password != _webApiOptions.Password)
|
||||
{
|
||||
e.Context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"ThingsGateway\"");
|
||||
return e.Context.Response
|
||||
.SetStatus(401, "Invalid Username or Password")
|
||||
.AnswerAsync();
|
||||
}
|
||||
|
||||
return e.InvokeNext();
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ internal sealed class PluginService : IPluginService
|
||||
/// </summary>
|
||||
public const string DirName = "GatewayPlugins";
|
||||
|
||||
private const string CacheKeyGetPluginOutputs = $"{ThingsGatewayCacheConst.Cache_Prefix}{nameof(PluginService)}{nameof(GetList)}";
|
||||
private const string CacheKeyGetPluginOutputs = $"ThingsGateway.Gateway.Application.{nameof(PluginService)}{nameof(GetList)}";
|
||||
private const string SaveEx = ".save";
|
||||
private const string DelEx = ".del";
|
||||
|
||||
@@ -78,12 +78,12 @@ internal sealed class PluginService : IPluginService
|
||||
|
||||
public Type GetDebugUI(string pluginName)
|
||||
{
|
||||
using var driver = GetDriver(pluginName);
|
||||
var driver = GetDriver(pluginName);
|
||||
return driver?.DriverDebugUIType;
|
||||
}
|
||||
public Type GetAddressUI(string pluginName)
|
||||
{
|
||||
using var driver = GetDriver(pluginName);
|
||||
var driver = GetDriver(pluginName);
|
||||
return driver?.DriverVariableAddressUIType;
|
||||
}
|
||||
|
||||
@@ -167,7 +167,6 @@ internal sealed class PluginService : IPluginService
|
||||
{
|
||||
string cacheKey = $"{nameof(PluginService)}_{nameof(GetDriverMethodInfos)}_{CultureInfo.CurrentUICulture.Name}";
|
||||
// 如果未提供驱动基类对象,则尝试根据插件名称获取驱动对象
|
||||
var dispose = driver == null; // 标记是否需要释放驱动对象
|
||||
driver ??= GetDriver(pluginName); // 如果未提供驱动对象,则根据插件名称获取驱动对象
|
||||
|
||||
// 检查插件名称是否为空或null
|
||||
@@ -183,10 +182,10 @@ internal sealed class PluginService : IPluginService
|
||||
}
|
||||
|
||||
// 如果未从缓存中获取到指定插件的属性信息,则尝试从驱动基类对象中获取
|
||||
return SetDriverMethodInfosCache(driver, pluginName, cacheKey, dispose); // 获取并设置属性信息缓存
|
||||
return SetDriverMethodInfosCache(driver, pluginName, cacheKey); // 获取并设置属性信息缓存
|
||||
|
||||
// 用于设置驱动方法信息缓存的内部方法
|
||||
List<DriverMethodInfo> SetDriverMethodInfosCache(IDriver driver, string pluginName, string cacheKey, bool dispose)
|
||||
List<DriverMethodInfo> SetDriverMethodInfosCache(IDriver driver, string pluginName, string cacheKey)
|
||||
{
|
||||
// 获取驱动对象的方法信息,并筛选出带有 DynamicMethodAttribute 特性的方法
|
||||
var dependencyPropertyWithInfos = driver.GetType().GetMethods()?.SelectMany(it =>
|
||||
@@ -206,10 +205,6 @@ internal sealed class PluginService : IPluginService
|
||||
var result = dependencyPropertyWithInfos.ToList();
|
||||
App.CacheService.HashAdd(cacheKey, pluginName, result);
|
||||
|
||||
// 如果是通过方法内部创建的驱动对象,则在方法执行完成后释放该驱动对象
|
||||
if (dispose)
|
||||
driver.SafeDispose();
|
||||
|
||||
// 返回获取到的属性信息字典
|
||||
return result;
|
||||
}
|
||||
@@ -228,7 +223,6 @@ internal sealed class PluginService : IPluginService
|
||||
{
|
||||
string cacheKey = $"{nameof(PluginService)}_{nameof(GetDriverPropertyTypes)}_{CultureInfo.CurrentUICulture.Name}";
|
||||
|
||||
var dispose = driver == null;
|
||||
driver ??= GetDriver(pluginName); // 如果 driver 为 null, 获取驱动实例
|
||||
// 检查插件名称是否为空或空字符串
|
||||
if (!pluginName.IsNullOrEmpty())
|
||||
@@ -245,17 +239,15 @@ internal sealed class PluginService : IPluginService
|
||||
}
|
||||
// 如果缓存中不存在该插件的数据,则重新获取并缓存
|
||||
|
||||
return (SetCache(driver, pluginName, cacheKey, dispose), driver.DriverProperties, driver.DriverPropertyUIType); // 调用 SetCache 方法进行缓存并返回结果
|
||||
return (SetCache(driver, pluginName, cacheKey), driver.DriverProperties, driver.DriverPropertyUIType); // 调用 SetCache 方法进行缓存并返回结果
|
||||
|
||||
// 定义 SetCache 方法,用于设置缓存并返回
|
||||
IEnumerable<IEditorItem> SetCache(IDriver driver, string pluginName, string cacheKey, bool dispose)
|
||||
IEnumerable<IEditorItem> SetCache(IDriver driver, string pluginName, string cacheKey)
|
||||
{
|
||||
var editorItems = PluginServiceUtil.GetEditorItems(driver.DriverProperties?.GetType()).ToList();
|
||||
// 将结果存入缓存中,键为插件名称
|
||||
App.CacheService.HashAdd(cacheKey, pluginName, editorItems);
|
||||
// 如果 dispose 参数为 true,则释放 driver 对象
|
||||
if (dispose)
|
||||
driver.SafeDispose();
|
||||
|
||||
return editorItems;
|
||||
}
|
||||
}
|
||||
@@ -291,7 +283,6 @@ internal sealed class PluginService : IPluginService
|
||||
|
||||
{
|
||||
string cacheKey = $"{nameof(PluginService)}_{nameof(GetVariablePropertyTypes)}_{CultureInfo.CurrentUICulture.Name}";
|
||||
var dispose = businessBase == null;
|
||||
businessBase ??= (BusinessBase)GetDriver(pluginName); // 如果 driver 为 null, 获取驱动实例
|
||||
|
||||
var data = App.CacheService.HashGetAll<List<IEditorItem>>(cacheKey);
|
||||
@@ -309,8 +300,6 @@ internal sealed class PluginService : IPluginService
|
||||
// 将结果存入缓存中,键为插件名称
|
||||
App.CacheService.HashAdd(cacheKey, pluginName, editorItems);
|
||||
// 如果 dispose 参数为 true,则释放 driver 对象
|
||||
if (dispose)
|
||||
businessBase.SafeDispose();
|
||||
return editorItems;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public static class PluginServiceUtil
|
||||
{
|
||||
{ "title", classAttribute.Remark }
|
||||
};
|
||||
tc.ComponentParameters.AddItem(
|
||||
tc.ComponentParameters = tc.ComponentParameters.AddItem(
|
||||
new("title", classAttribute.Remark)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class TimeIntervalTriggerNode : TextNode, ITriggerNode, IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
_task?.Stop();
|
||||
_task.TryDispose();
|
||||
_task?.TryDispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@@ -151,13 +151,13 @@ internal static class RuntimeServiceHelper
|
||||
|
||||
public static async Task<List<ChannelRuntime>> GetNewChannelRuntimesAsync(HashSet<long> ids)
|
||||
{
|
||||
var newChannelRuntimes = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).Where(a => ids.Contains(a.Id)).AdaptListChannelRuntime();
|
||||
var newChannelRuntimes = (await GlobalData.ChannelService.GetFromDBAsync((a => ids.Contains(a.Id))).ConfigureAwait(false)).AdaptListChannelRuntime();
|
||||
return newChannelRuntimes;
|
||||
}
|
||||
|
||||
public static async Task<List<DeviceRuntime>> GetNewDeviceRuntimesAsync(HashSet<long> deviceids)
|
||||
{
|
||||
var newDeviceRuntimes = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).Where(a => deviceids.Contains(a.Id)).AdaptListDeviceRuntime();
|
||||
var newDeviceRuntimes = (await GlobalData.DeviceService.GetFromDBAsync((a => deviceids.Contains(a.Id))).ConfigureAwait(false)).AdaptListDeviceRuntime();
|
||||
return newDeviceRuntimes;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,6 @@ internal interface IVariableService
|
||||
|
||||
Task<List<Variable>> GetByDeviceIdAsync(List<long> deviceIds);
|
||||
void DeleteVariableCache();
|
||||
ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, Dictionary<string, Device> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows);
|
||||
ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows);
|
||||
List<VariableRuntime> GetAllVariableRuntime();
|
||||
}
|
||||
|
||||
@@ -375,37 +375,37 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
HashSet<long>? deviceId = null;
|
||||
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
||||
{
|
||||
var channel = (await _channelService.GetAllAsync().ConfigureAwait(false)).Where(a => a.PluginName == exportFilter.PluginName).Select(a => a.Id).ToHashSet();
|
||||
deviceId = (await _deviceService.GetAllAsync().ConfigureAwait(false)).Where(a => channel.Contains(a.ChannelId)).Select(a => a.Id).ToHashSet();
|
||||
var channel = (GlobalData.IdChannels).Where(a => a.Value.PluginName == exportFilter.PluginName).Select(a => a.Value.Id).ToHashSet();
|
||||
deviceId = (GlobalData.IdDevices).Where(a => channel.Contains(a.Value.ChannelId)).Select(a => a.Value.Id).ToHashSet();
|
||||
}
|
||||
else if (exportFilter.ChannelId != null)
|
||||
{
|
||||
deviceId = (await _deviceService.GetAllAsync().ConfigureAwait(false)).Where(a => a.ChannelId == exportFilter.ChannelId).Select(a => a.Id).ToHashSet();
|
||||
deviceId = (GlobalData.IdDevices).Where(a => a.Value.ChannelId == exportFilter.ChannelId).Select(a => a.Value.Id).ToHashSet();
|
||||
}
|
||||
var whereQuery = (ISugarQueryable<Variable> a) => a
|
||||
.WhereIF(!exportFilter.QueryPageOptions.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(exportFilter.QueryPageOptions.SearchText!))
|
||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
|
||||
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
|
||||
|
||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
||||
|
||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()));
|
||||
return whereQuery;
|
||||
}
|
||||
|
||||
private async Task<Func<IEnumerable<Variable>, IEnumerable<Variable>>> GetWhereEnumerableFunc(ExportFilter exportFilter)
|
||||
private async Task<Func<IEnumerable<Variable>, IEnumerable<Variable>>> GetWhereEnumerableFunc(ExportFilter exportFilter, bool sql = false)
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
HashSet<long>? deviceId = null;
|
||||
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
||||
{
|
||||
var channel = (await _channelService.GetAllAsync().ConfigureAwait(false)).Where(a => a.PluginName == exportFilter.PluginName).Select(a => a.Id).ToHashSet();
|
||||
deviceId = (await _deviceService.GetAllAsync().ConfigureAwait(false)).Where(a => channel.Contains(a.ChannelId)).Select(a => a.Id).ToHashSet();
|
||||
var channel = (GlobalData.IdChannels).Where(a => a.Value.PluginName == exportFilter.PluginName).Select(a => a.Value.Id).ToHashSet();
|
||||
deviceId = (GlobalData.IdDevices).Where(a => channel.Contains(a.Value.ChannelId)).Select(a => a.Value.Id).ToHashSet();
|
||||
}
|
||||
else if (exportFilter.ChannelId != null)
|
||||
{
|
||||
deviceId = (await _deviceService.GetAllAsync().ConfigureAwait(false)).Where(a => a.ChannelId == exportFilter.ChannelId).Select(a => a.Id).ToHashSet();
|
||||
deviceId = (GlobalData.IdDevices).Where(a => a.Value.ChannelId == exportFilter.ChannelId).Select(a => a.Value.Id).ToHashSet();
|
||||
}
|
||||
var whereQuery = (IEnumerable<Variable> a) => a
|
||||
.WhereIF(!exportFilter.QueryPageOptions.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(exportFilter.QueryPageOptions.SearchText!))
|
||||
@@ -415,7 +415,12 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
||||
|
||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()));
|
||||
.WhereIF(sql && exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()))
|
||||
.WhereIF(!sql && exportFilter.PluginType == PluginTypeEnum.Business && exportFilter.DeviceId > 0, u =>
|
||||
GlobalData.IdVariables.TryGetValue(u.Id, out var runtime) &&
|
||||
GlobalData.ContainsVariable(exportFilter.DeviceId.Value, runtime)
|
||||
|
||||
);
|
||||
return whereQuery;
|
||||
}
|
||||
|
||||
@@ -442,7 +447,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
|
||||
public void DeleteVariableCache()
|
||||
{
|
||||
App.CacheService.Remove(ThingsGatewayCacheConst.Cache_Variable);
|
||||
//App.CacheService.Remove(ThingsGatewayCacheConst.Cache_Variable);
|
||||
}
|
||||
|
||||
public List<VariableRuntime> GetAllVariableRuntime()
|
||||
@@ -461,14 +466,14 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
[OperDesc("ExportVariable", isRecordPar: false, localizerType: typeof(Variable))]
|
||||
public async Task<MemoryStream> ExportMemoryStream(List<Variable> variables, string deviceName = 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 = variables.Where(a => a.VariablePropertys?.Count > 0).SelectMany(a => a.VariablePropertys).Select(a =>
|
||||
{
|
||||
if (deviceDicts.TryGetValue(a.Key, out var device) && channelDicts.TryGetValue(device.ChannelId, out var channel))
|
||||
{
|
||||
var pluginKey = channel?.PluginName;
|
||||
using var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey);
|
||||
var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey);
|
||||
return new KeyValuePair<string, VariablePropertyBase>(pluginKey, businessBase.VariablePropertys);
|
||||
}
|
||||
return new KeyValuePair<string, VariablePropertyBase>(string.Empty, null);
|
||||
@@ -493,14 +498,14 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
//导出
|
||||
var variables = GlobalData.IdVariables.Select(a => a.Value).GetQuery(exportFilter.QueryPageOptions, whereQuery, exportFilter.FilterKeyValueAction);
|
||||
|
||||
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 = variables.Where(a => a.VariablePropertys?.Count > 0).SelectMany(a => a.VariablePropertys).Select(a =>
|
||||
{
|
||||
if (deviceDicts.TryGetValue(a.Key, out var device) && channelDicts.TryGetValue(device.ChannelId, out var channel))
|
||||
{
|
||||
var pluginKey = channel?.PluginName;
|
||||
using var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey);
|
||||
var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey);
|
||||
return new KeyValuePair<string, VariablePropertyBase>(pluginKey, businessBase.VariablePropertys);
|
||||
}
|
||||
return new KeyValuePair<string, VariablePropertyBase>(string.Empty, null);
|
||||
@@ -512,8 +517,11 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = (await PageAsync(exportFilter).ConfigureAwait(false));
|
||||
var sheets = await VariableServiceHelpers.ExportCoreAsync(data.Items, sortName: exportFilter.QueryPageOptions.SortName, sortOrder: exportFilter.QueryPageOptions.SortOrder).ConfigureAwait(false);
|
||||
var whereQuery = await GetWhereEnumerableFunc(exportFilter).ConfigureAwait(false);
|
||||
//导出
|
||||
var data = GlobalData.IdVariables.Select(a => a.Value).GetQuery(exportFilter.QueryPageOptions, whereQuery, exportFilter.FilterKeyValueAction);
|
||||
//var data = (await PageAsync(exportFilter).ConfigureAwait(false));
|
||||
var sheets = VariableServiceHelpers.ExportCore(data, sortName: exportFilter.QueryPageOptions.SortName, sortOrder: exportFilter.QueryPageOptions.SortOrder);
|
||||
return sheets;
|
||||
}
|
||||
}
|
||||
@@ -578,7 +586,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
var sheetNames = MiniExcel.GetSheetNames(path);
|
||||
|
||||
// 获取所有设备的字典,以设备名称作为键
|
||||
var deviceDicts = (await _deviceService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
|
||||
var deviceDicts = GlobalData.Devices;
|
||||
|
||||
// 存储导入检验结果的字典
|
||||
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
|
||||
@@ -609,7 +617,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
}
|
||||
}
|
||||
|
||||
public ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, Dictionary<string, Device> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
|
||||
public ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
|
||||
{
|
||||
// 变量页处理
|
||||
if (sheetName == ExportString.VariableName)
|
||||
|
||||
@@ -21,16 +21,16 @@ namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public static class VariableServiceHelpers
|
||||
{
|
||||
public static async Task<USheetDatas> ExportVariableAsync(IEnumerable<Variable> variables, string sortName = nameof(Variable.Id), SortOrder sortOrder = SortOrder.Asc)
|
||||
public static USheetDatas ExportVariable(IEnumerable<Variable> variables, string sortName = nameof(Variable.Id), SortOrder sortOrder = SortOrder.Asc)
|
||||
{
|
||||
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 = variables.Where(a => a.VariablePropertys?.Count > 0).SelectMany(a => a.VariablePropertys).Select(a =>
|
||||
{
|
||||
if (deviceDicts.TryGetValue(a.Key, out var device) && channelDicts.TryGetValue(device.ChannelId, out var channel))
|
||||
{
|
||||
var pluginKey = channel?.PluginName;
|
||||
using var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey);
|
||||
var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey);
|
||||
return new KeyValuePair<string, VariablePropertyBase>(pluginKey, businessBase.VariablePropertys);
|
||||
}
|
||||
return new KeyValuePair<string, VariablePropertyBase>(string.Empty, null);
|
||||
@@ -41,8 +41,8 @@ public static class VariableServiceHelpers
|
||||
static IAsyncEnumerable<Variable> FilterPluginDevices(
|
||||
IAsyncEnumerable<Variable> data,
|
||||
string pluginName,
|
||||
Dictionary<long, Device> deviceDicts,
|
||||
Dictionary<long, Channel> channelDicts)
|
||||
IReadOnlyDictionary<long, DeviceRuntime> deviceDicts,
|
||||
IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
|
||||
{
|
||||
return data.Where(variable =>
|
||||
{
|
||||
@@ -65,8 +65,8 @@ Dictionary<long, Device> deviceDicts,
|
||||
static IEnumerable<Variable> FilterPluginDevices(
|
||||
IEnumerable<Variable> data,
|
||||
string pluginName,
|
||||
Dictionary<long, Device> deviceDicts,
|
||||
Dictionary<long, Channel> channelDicts)
|
||||
IReadOnlyDictionary<long, DeviceRuntime> deviceDicts,
|
||||
IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
|
||||
{
|
||||
return data.Where(variable =>
|
||||
{
|
||||
@@ -88,8 +88,8 @@ Dictionary<long, Channel> channelDicts)
|
||||
|
||||
public static Dictionary<string, object> ExportSheets(
|
||||
IEnumerable<Variable> data,
|
||||
Dictionary<long, Device> deviceDicts,
|
||||
Dictionary<long, Channel> channelDicts,
|
||||
IReadOnlyDictionary<long, DeviceRuntime> deviceDicts,
|
||||
IReadOnlyDictionary<long, ChannelRuntime> channelDicts,
|
||||
Dictionary<string, VariablePropertyBase> pluginDrivers,
|
||||
string? deviceName = null)
|
||||
{
|
||||
@@ -112,7 +112,7 @@ Dictionary<long, Channel> channelDicts)
|
||||
|
||||
static IEnumerable<Dictionary<string, object>> GetVariableSheets(
|
||||
IEnumerable<Variable> data,
|
||||
Dictionary<long, Device> deviceDicts,
|
||||
IReadOnlyDictionary<long, DeviceRuntime> deviceDicts,
|
||||
string? deviceName)
|
||||
{
|
||||
var type = typeof(Variable);
|
||||
@@ -132,7 +132,7 @@ Dictionary<long, Channel> channelDicts)
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> GetVariable(Dictionary<long, Device> deviceDicts, string? deviceName, Type type, IOrderedEnumerable<PropertyInfo> propertyInfos, Variable variable)
|
||||
private static Dictionary<string, object> GetVariable(IReadOnlyDictionary<long, DeviceRuntime> deviceDicts, string? deviceName, Type type, IOrderedEnumerable<PropertyInfo> propertyInfos, Variable variable)
|
||||
{
|
||||
var row = new Dictionary<string, object>();
|
||||
deviceDicts.TryGetValue(variable.DeviceId, out var device);
|
||||
@@ -153,8 +153,8 @@ Dictionary<long, Channel> channelDicts)
|
||||
|
||||
static IEnumerable<Dictionary<string, object>> GetPluginSheets(
|
||||
IEnumerable<Variable> data,
|
||||
Dictionary<long, Device> deviceDicts,
|
||||
Dictionary<long, Channel> channelDicts,
|
||||
IReadOnlyDictionary<long, DeviceRuntime> deviceDicts,
|
||||
IReadOnlyDictionary<long, ChannelRuntime> channelDicts,
|
||||
string plugin,
|
||||
Dictionary<string, VariablePropertyBase> pluginDrivers,
|
||||
ConcurrentDictionary<string, (VariablePropertyBase, Dictionary<string, PropertyInfo>)> propertysDict)
|
||||
@@ -224,8 +224,8 @@ Dictionary<long, Channel> channelDicts)
|
||||
|
||||
public static Dictionary<string, object> ExportSheets(
|
||||
IAsyncEnumerable<Variable> data,
|
||||
Dictionary<long, Device> deviceDicts,
|
||||
Dictionary<long, Channel> channelDicts,
|
||||
IReadOnlyDictionary<long, DeviceRuntime> deviceDicts,
|
||||
IReadOnlyDictionary<long, ChannelRuntime> channelDicts,
|
||||
Dictionary<string, VariablePropertyBase> pluginDrivers,
|
||||
string? deviceName = null)
|
||||
{
|
||||
@@ -248,7 +248,7 @@ Dictionary<long, Channel> channelDicts)
|
||||
|
||||
static async IAsyncEnumerable<Dictionary<string, object>> GetVariableSheets(
|
||||
IAsyncEnumerable<Variable> data,
|
||||
Dictionary<long, Device> deviceDicts,
|
||||
IReadOnlyDictionary<long, DeviceRuntime> deviceDicts,
|
||||
string? deviceName)
|
||||
{
|
||||
var type = typeof(Variable);
|
||||
@@ -270,8 +270,8 @@ Dictionary<long, Channel> channelDicts)
|
||||
|
||||
static async IAsyncEnumerable<Dictionary<string, object>> GetPluginSheets(
|
||||
IAsyncEnumerable<Variable> data,
|
||||
Dictionary<long, Device> deviceDicts,
|
||||
Dictionary<long, Channel> channelDicts,
|
||||
IReadOnlyDictionary<long, DeviceRuntime> deviceDicts,
|
||||
IReadOnlyDictionary<long, ChannelRuntime> channelDicts,
|
||||
string plugin,
|
||||
Dictionary<string, VariablePropertyBase> pluginDrivers,
|
||||
ConcurrentDictionary<string, (VariablePropertyBase, Dictionary<string, PropertyInfo>)> propertysDict)
|
||||
@@ -314,14 +314,14 @@ Dictionary<long, Channel> channelDicts)
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<string, object>> ExportCoreAsync(IEnumerable<Variable> data, string deviceName = null, string sortName = nameof(Variable.Id), SortOrder sortOrder = SortOrder.Asc)
|
||||
public static Dictionary<string, object> ExportCore(IEnumerable<Variable> data, string deviceName = null, string sortName = nameof(Variable.Id), SortOrder sortOrder = SortOrder.Asc)
|
||||
{
|
||||
if (data?.Any() != true)
|
||||
{
|
||||
data = new List<Variable>();
|
||||
}
|
||||
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 driverPluginDicts = GlobalData.PluginService.GetList(PluginTypeEnum.Business).ToDictionary(a => a.FullName);
|
||||
//总数据
|
||||
Dictionary<string, object> sheets = new();
|
||||
@@ -503,7 +503,7 @@ Dictionary<long, Channel> channelDicts)
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
|
||||
// 获取所有设备的字典,以设备名称作为键
|
||||
var deviceDicts = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);
|
||||
var deviceDicts = GlobalData.Devices;
|
||||
|
||||
// 存储导入检验结果的字典
|
||||
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user