mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-25 20:53:10 +08:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e97ee9b64b | ||
|
|
6a03e39eeb | ||
|
|
525ec740b5 | ||
|
|
b790cf5f4e | ||
|
|
d1248811fd | ||
|
|
022d016e8e | ||
|
|
f73245e650 | ||
|
|
484461fa05 | ||
|
|
7e0b7aff2a | ||
|
|
6c450dcb09 | ||
|
|
227f44283f | ||
|
|
74f6e79625 | ||
|
|
cec43e2ce8 | ||
|
|
7553b258bb | ||
|
|
8bdbdc117e | ||
|
|
0e206be296 | ||
|
|
00b7353433 | ||
|
|
44e7a83593 | ||
|
|
dd68d555d4 | ||
|
|
0456296103 | ||
|
|
a1b66277ff | ||
|
|
50758b79bc | ||
|
|
06a1f902ad | ||
|
|
58f8b23b7c | ||
|
|
9e7c348b15 | ||
|
|
5f5ff8b43b | ||
|
|
f626b4e5fc | ||
|
|
bc23200e66 | ||
|
|
95ab59fd5a | ||
|
|
0bbee003b0 | ||
|
|
d20cb7a928 | ||
|
|
952c134aba | ||
|
|
5533d980e3 | ||
|
|
aa0213818f | ||
|
|
af88ffd57c | ||
|
|
87447b1c2a | ||
|
|
384a12880b |
@@ -20,11 +20,14 @@ A cross-platform, high-performance edge data collection gateway based on net9.
|
||||
## Demo
|
||||
|
||||
|
||||
[Demo](http://47.119.161.158:5000/)
|
||||
[Demo](https://demo.thingsgateway.cn/)
|
||||
|
||||
|
||||
Account: **SuperAdmin**
|
||||
|
||||
|
||||
Password: **111111**
|
||||
|
||||
|
||||
**In the upper-right corner, switch to the IoT Gateway module in the personal popup box**
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
## 演示
|
||||
|
||||
[ThingsGateway演示地址](http://47.119.161.158:5000/)
|
||||
[ThingsGateway演示地址](https://demo.thingsgateway.cn/)
|
||||
|
||||
账户 : **SuperAdmin**
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ public sealed class OperDescAttribute : MoAttribute
|
||||
Name = (localizerType == null ? App.CreateLocalizerByType(typeof(OperDescAttribute)) : App.CreateLocalizerByType(localizerType))![Description],
|
||||
Category = LogCateGoryEnum.Operate,
|
||||
ExeStatus = true,
|
||||
OpIp = AppService?.RemoteIpAddress?.MapToIPv4()?.ToString() ?? string.Empty,
|
||||
OpIp = AppService?.RemoteIpAddress ?? string.Empty,
|
||||
OpBrowser = clientInfo?.UA?.Family + clientInfo?.UA?.Major,
|
||||
OpOs = clientInfo?.OS?.Family + clientInfo?.OS?.Major,
|
||||
OpTime = DateTime.Now,
|
||||
|
||||
@@ -23,7 +23,7 @@ public class SysDict : BaseEntity
|
||||
/// <summary>
|
||||
/// 类型
|
||||
///</summary>
|
||||
[SugarColumn(ColumnDescription = "类型", Length = 200)]
|
||||
[SugarColumn(ColumnDescription = "类型")]
|
||||
[AutoGenerateColumn(Ignore = true, Filterable = true, Sortable = true)]
|
||||
public virtual DictTypeEnum DictType { get; set; }
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public class SysOperateLog
|
||||
/// <summary>
|
||||
/// 日志分类
|
||||
///</summary>
|
||||
[SugarColumn(ColumnDescription = "日志分类", Length = 200)]
|
||||
[SugarColumn(ColumnDescription = "日志分类")]
|
||||
[AutoGenerateColumn(Order = 1, Filterable = true, Sortable = true)]
|
||||
public LogCateGoryEnum Category { get; set; }
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class SysPosition : BaseEntity
|
||||
/// <summary>
|
||||
/// 分类
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "Category", ColumnDescription = "分类", Length = 200)]
|
||||
[SugarColumn(ColumnName = "Category", ColumnDescription = "分类")]
|
||||
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
|
||||
public virtual PositionCategoryEnum Category { get; set; }
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class SysRelation : PrimaryKeyEntity
|
||||
/// <summary>
|
||||
/// 分类
|
||||
///</summary>
|
||||
[SugarColumn(ColumnDescription = "分类", Length = 200)]
|
||||
[SugarColumn(ColumnDescription = "分类")]
|
||||
public RelationCategoryEnum Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -41,7 +41,7 @@ public class SysRole : BaseEntity
|
||||
/// <summary>
|
||||
/// 分类
|
||||
///</summary>
|
||||
[SugarColumn(ColumnDescription = "分类", Length = 200, IsNullable = false)]
|
||||
[SugarColumn(ColumnDescription = "分类", IsNullable = false)]
|
||||
[AutoGenerateColumn(Visible = true, Sortable = true, Filterable = true)]
|
||||
public virtual RoleCategoryEnum Category { get; set; }
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
|
||||
using UAParser;
|
||||
@@ -72,7 +71,7 @@ public class AppService : IAppService
|
||||
}
|
||||
public ClaimsPrincipal? User => App.User;
|
||||
|
||||
public IPAddress? RemoteIpAddress => App.HttpContext?.Connection?.RemoteIpAddress;
|
||||
public string? RemoteIpAddress => App.HttpContext?.GetRemoteIpAddressToIPv4();
|
||||
|
||||
public int LocalPort => App.HttpContext.Connection.LocalPort;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
|
||||
using UAParser;
|
||||
@@ -24,7 +23,7 @@ public class HybridAppService : IAppService
|
||||
{
|
||||
var str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0";
|
||||
ClientInfo = Parser.GetDefault().Parse(str);
|
||||
RemoteIpAddress = IPAddress.Parse("127.0.0.1");
|
||||
RemoteIpAddress = "127.0.0.1";
|
||||
}
|
||||
public ClientInfo? ClientInfo { get; }
|
||||
|
||||
@@ -56,7 +55,7 @@ public class HybridAppService : IAppService
|
||||
}
|
||||
}
|
||||
|
||||
public IPAddress? RemoteIpAddress { get; }
|
||||
public string? RemoteIpAddress { get; }
|
||||
|
||||
public string GetReturnUrl(string returnUrl)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
|
||||
using UAParser;
|
||||
@@ -31,7 +30,7 @@ public interface IAppService
|
||||
/// <summary>
|
||||
/// RemoteIpAddress
|
||||
/// </summary>
|
||||
public IPAddress? RemoteIpAddress { get; }
|
||||
public string? RemoteIpAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GetReturnUrl
|
||||
|
||||
@@ -105,7 +105,7 @@ public class AuthService : IAuthService
|
||||
{
|
||||
var loginEvent = new LoginEvent
|
||||
{
|
||||
Ip = _appService.RemoteIpAddress?.MapToIPv4()?.ToString(),
|
||||
Ip = _appService.RemoteIpAddress,
|
||||
SysUser = userinfo,
|
||||
VerificatId = verificatId
|
||||
};
|
||||
@@ -236,7 +236,7 @@ public class AuthService : IAuthService
|
||||
//登录事件参数
|
||||
var logingEvent = new LoginEvent
|
||||
{
|
||||
Ip = _appService.RemoteIpAddress?.MapToIPv4()?.ToString(),
|
||||
Ip = _appService.RemoteIpAddress,
|
||||
Device = App.GetService<IAppService>().ClientInfo?.OS?.ToString(),
|
||||
Expire = expire,
|
||||
SysUser = sysUser,
|
||||
|
||||
@@ -47,12 +47,10 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<bool> DeleteAsync(IEnumerable<T> models)
|
||||
public async Task<bool> DeleteAsync(IEnumerable<T> models)
|
||||
{
|
||||
if (models.FirstOrDefault() is IPrimaryIdEntity)
|
||||
return DeleteAsync(models.Select(a => ((IPrimaryIdEntity)a).Id));
|
||||
else
|
||||
return Task.FromResult(false);
|
||||
using var db = GetDB();
|
||||
return await db.Deleteable<T>().In(models.ToList()).ExecuteCommandHasChangeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -165,4 +163,6 @@ public class BaseService<T> : IDataService<T>, IDisposable where T : class, new(
|
||||
{
|
||||
return DbContext.Db.GetConnectionScopeWithAttr<T>().CopyNew();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -103,4 +103,46 @@ public static class DbContext
|
||||
Console.WriteLine("【Sql执行时间】:" + DateTime.Now.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static async Task BulkCopyAsync<TITEM>(this SqlSugarClient db, List<TITEM> datas, int size) where TITEM : class, new()
|
||||
{
|
||||
switch (db.CurrentConnectionConfig.DbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
case DbType.SqlServer:
|
||||
case DbType.Sqlite:
|
||||
case DbType.Oracle:
|
||||
case DbType.PostgreSQL:
|
||||
case DbType.Dm:
|
||||
case DbType.MySqlConnector:
|
||||
case DbType.Kdbndp:
|
||||
await db.Fastest<TITEM>().PageSize(size).BulkCopyAsync(datas).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
await db.Insertable(datas).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
public static async Task BulkUpdateAsync<TITEM>(this SqlSugarClient db, List<TITEM> datas, int size) where TITEM : class, new()
|
||||
{
|
||||
switch (db.CurrentConnectionConfig.DbType)
|
||||
{
|
||||
case DbType.MySql:
|
||||
case DbType.SqlServer:
|
||||
case DbType.Sqlite:
|
||||
case DbType.Oracle:
|
||||
case DbType.PostgreSQL:
|
||||
case DbType.Dm:
|
||||
case DbType.MySqlConnector:
|
||||
case DbType.Kdbndp:
|
||||
await db.Fastest<TITEM>().PageSize(size).BulkUpdateAsync(datas).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
await db.Updateable(datas).PageSize(size).ExecuteCommandAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using SqlSugar;
|
||||
@@ -23,7 +24,7 @@ namespace ThingsGateway.Admin.Application;
|
||||
[AppStartup(1000000000)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureAdminApp(IServiceCollection services)
|
||||
public void Configure(IServiceCollection services)
|
||||
{
|
||||
Directory.CreateDirectory("DB");
|
||||
|
||||
@@ -75,7 +76,7 @@ public class Startup : AppStartup
|
||||
|
||||
}
|
||||
|
||||
public void UseAdminCore(IServiceProvider serviceProvider)
|
||||
public void Use(IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
//检查ConfigId
|
||||
var configIdGroup = DbContext.DbConfigs.GroupBy(it => it.ConfigId);
|
||||
|
||||
@@ -18,11 +18,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.2" />
|
||||
<!--<PackageReference Include="MiniExcel" Version="1.39.0" />-->
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.4" />
|
||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.188" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.191" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ThingsGateway.Admin.Razor;
|
||||
[AppStartup(-1)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureAdminApp(IServiceCollection services)
|
||||
public void Configure(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IMenuService, MenuService>();
|
||||
services.AddBootstrapBlazorTableExportService();
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
|
||||
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
@@ -17,6 +19,7 @@ using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
@@ -27,6 +30,7 @@ using ThingsGateway.Admin.Razor;
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Logging;
|
||||
using ThingsGateway.NewLife.Caching;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace ThingsGateway.AdminServer;
|
||||
|
||||
@@ -291,6 +295,21 @@ public class Startup : AppStartup
|
||||
services.AddAuthorizationCore();
|
||||
services.AddScoped<IAuthorizationHandler, BlazorServerAuthenticationHandler>();
|
||||
services.AddScoped<AuthenticationStateProvider, BlazorServerAuthenticationStateProvider>();
|
||||
|
||||
#if NET9_0_OR_GREATER
|
||||
var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
#else
|
||||
var certificate = new X509Certificate2("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
#endif
|
||||
services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo("../keys"))
|
||||
.ProtectKeysWithCertificate(certificate)
|
||||
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
|
||||
{
|
||||
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
|
||||
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -298,9 +317,9 @@ public class Startup : AppStartup
|
||||
public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env)
|
||||
{
|
||||
var app = (WebApplication)applicationBuilder;
|
||||
app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All, KnownNetworks = { }, KnownProxies = { } });
|
||||
app.UseBootstrapBlazor();
|
||||
|
||||
app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All });
|
||||
|
||||
// 启用本地化
|
||||
var option = app.Services.GetService<IOptions<RequestLocalizationOptions>>();
|
||||
|
||||
@@ -72,6 +72,9 @@
|
||||
<None Update="pm2-linux.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ThingsGateway.pfx">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="thingsgateway.service">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
BIN
src/Admin/ThingsGateway.AdminServer/ThingsGateway.pfx
Normal file
BIN
src/Admin/ThingsGateway.AdminServer/ThingsGateway.pfx
Normal file
Binary file not shown.
@@ -106,7 +106,7 @@ public static class HttpContextExtensions
|
||||
/// <param name="context"></param>
|
||||
/// <param name="xff">是否优先取 X-Forwarded-For</param>
|
||||
/// <returns></returns>
|
||||
public static string GetRemoteIpAddressToIPv4(this HttpContext context, bool xff = false)
|
||||
public static string GetRemoteIpAddressToIPv4(this HttpContext context, bool xff = true)
|
||||
{
|
||||
var ipv4 = context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString();
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ internal sealed class ScheduleHostedService : BackgroundService
|
||||
await BackgroundProcessing(stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_logger.LogCritical($"Schedule hosted service is stopped.");
|
||||
_logger.LogInformation($"Schedule hosted service is stopped.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -32,7 +32,7 @@ public class TimeTick
|
||||
/// <summary>
|
||||
/// 上次触发时间
|
||||
/// </summary>
|
||||
public DateTime LastTime { get; private set; } = DateTime.Now;
|
||||
public DateTime LastTime { get; private set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// 是否触发时间刻度
|
||||
@@ -62,7 +62,7 @@ public class TimeTick
|
||||
return result;
|
||||
}
|
||||
|
||||
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = true)
|
||||
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = false)
|
||||
{
|
||||
// 在没有 Cron 表达式的情况下,使用固定间隔
|
||||
if (cron == null)
|
||||
@@ -86,7 +86,7 @@ public class TimeTick
|
||||
|
||||
}
|
||||
|
||||
public DateTime GetNextTime(bool setLastTime = true) => GetNextTime(DateTime.UtcNow, setLastTime);
|
||||
public DateTime GetNextTime(bool setLastTime = false) => GetNextTime(DateTime.UtcNow, setLastTime);
|
||||
|
||||
/// <summary>
|
||||
/// 是否到达设置的时间间隔
|
||||
|
||||
@@ -22,12 +22,34 @@ public static class JSRuntimeExtensions
|
||||
/// 获取文化信息
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime"></param>
|
||||
public static ValueTask<string> GetCulture(this IJSRuntime jsRuntime) => jsRuntime.InvokeAsync<string>("getCultureLocalStorage");
|
||||
public static async ValueTask<string> GetCulture(this IJSRuntime jsRuntime)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await jsRuntime.InvokeAsync<string>("getCultureLocalStorage");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置文化信息
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime"></param>
|
||||
/// <param name="cultureName"></param>
|
||||
public static ValueTask SetCulture(this IJSRuntime jsRuntime, string cultureName) => jsRuntime.InvokeVoidAsync("setCultureLocalStorage", cultureName);
|
||||
public static async ValueTask SetCulture(this IJSRuntime jsRuntime, string cultureName)
|
||||
{
|
||||
try
|
||||
{
|
||||
await jsRuntime.InvokeVoidAsync("setCultureLocalStorage", cultureName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
@@ -48,7 +49,7 @@ public class Startup : AppStartup
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UseService(IServiceProvider serviceProvider)
|
||||
public void Use(IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.5.5" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.6.0" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.4.18</PluginVersion>
|
||||
<ProPluginVersion>10.4.18</ProPluginVersion>
|
||||
<PluginVersion>10.5.15</PluginVersion>
|
||||
<ProPluginVersion>10.5.15</ProPluginVersion>
|
||||
<AuthenticationVersion>2.1.7</AuthenticationVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CS-Script" Version="4.8.27" />
|
||||
<PackageReference Include="CS-Script" Version="4.9.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ThingsGateway.Debug;
|
||||
[AppStartup(100000000)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureAdminApp(IServiceCollection services)
|
||||
public void Configure(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPlatformService, PlatformService>();
|
||||
}
|
||||
|
||||
@@ -94,11 +94,11 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
return WaitLocks.GetOrAdd(key, (a) => new WaitLock(WaitLock.MaxCount));
|
||||
}
|
||||
|
||||
public override Task StopAsync()
|
||||
public override Task<Result> StopAsync(CancellationToken token)
|
||||
{
|
||||
WaitLocks.ForEach(a => a.Value.SafeDispose());
|
||||
WaitLocks.Clear();
|
||||
return base.StopAsync();
|
||||
return base.StopAsync(token);
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<EndPoint, WaitLock> _waitLocks = new();
|
||||
|
||||
@@ -128,17 +128,17 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
|
||||
public Protocol Protocol => new Protocol("Other");
|
||||
|
||||
public DateTime LastReceivedTime { get; private set; }
|
||||
public DateTimeOffset LastReceivedTime { get; private set; }
|
||||
|
||||
public DateTime LastSentTime { get; private set; }
|
||||
public DateTimeOffset LastSentTime { get; private set; }
|
||||
|
||||
public bool IsClient => true;
|
||||
|
||||
public bool Online => true;
|
||||
|
||||
public Task CloseAsync(string msg)
|
||||
public Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return Task.FromResult(Result.Success);
|
||||
}
|
||||
|
||||
public Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
|
||||
@@ -80,7 +80,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
if (HeartbeatByte.SequenceEqual(e.ByteBlock.AsSegment(0, len)))
|
||||
{
|
||||
if (DateTime.UtcNow - socket.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
|
||||
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
|
||||
try
|
||||
{
|
||||
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
|
||||
if (DateTimeOffset.Now - tcpClient.LastSentTime < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public static class PluginUtil
|
||||
.SetOnClose(async (c, t) =>
|
||||
{
|
||||
await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
c.SafeClose($"{channelOptions.CheckClearTime}ms Timeout");
|
||||
await c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout").ConfigureAwait(false);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -74,8 +74,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
|
||||
//private readonly WaitLock _connectLock = new WaitLock();
|
||||
/// <inheritdoc/>
|
||||
public override async Task CloseAsync(string msg)
|
||||
public override async Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
{
|
||||
|
||||
if (Online)
|
||||
{
|
||||
try
|
||||
@@ -83,11 +84,12 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
//await _connectLock.WaitAsync().ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
await base.CloseAsync(msg).ConfigureAwait(false);
|
||||
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -95,6 +97,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
//_connectLock.Release();
|
||||
}
|
||||
}
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -72,7 +72,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
|
||||
//private readonly WaitLock _connectLock = new WaitLock();
|
||||
/// <inheritdoc/>
|
||||
public override async Task CloseAsync(string msg)
|
||||
public override async Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
{
|
||||
if (Online)
|
||||
{
|
||||
@@ -81,11 +81,12 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
//await _connectLock.WaitAsync().ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
await base.CloseAsync(msg).ConfigureAwait(false);
|
||||
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -93,6 +94,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
//_connectLock.Release();
|
||||
}
|
||||
}
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -94,22 +94,22 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task StopAsync()
|
||||
public override async Task<Result> StopAsync(CancellationToken token)
|
||||
{
|
||||
if (Monitors.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
await _connectLock.WaitAsync().ConfigureAwait(false);
|
||||
await _connectLock.WaitAsync(token).ConfigureAwait(false);
|
||||
if (Monitors.Any())
|
||||
{
|
||||
|
||||
await ClearAsync().ConfigureAwait(false);
|
||||
var iPHost = Monitors.FirstOrDefault()?.Option.IpHost;
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
var result = await base.StopAsync(token).ConfigureAwait(false);
|
||||
if (!Monitors.Any())
|
||||
Logger?.Info($"{iPHost}{DefaultResource.Localizer["ServiceStoped"]}");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -120,8 +120,10 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
var result = await base.StopAsync(token).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
return Result.Success; ;
|
||||
}
|
||||
|
||||
|
||||
@@ -192,9 +194,9 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task CloseAsync(string msg)
|
||||
public Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
{
|
||||
return StopAsync();
|
||||
return StopAsync(token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -64,10 +64,10 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
public virtual WaitLock GetLock(string key) => WaitLock;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task CloseAsync(string msg)
|
||||
public override Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
return base.CloseAsync(msg);
|
||||
return base.CloseAsync(msg, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -73,9 +73,9 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task CloseAsync(string msg)
|
||||
public Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
{
|
||||
return StopAsync();
|
||||
return StopAsync(token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -127,26 +127,28 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task StopAsync()
|
||||
public override async Task<Result> StopAsync(CancellationToken token)
|
||||
{
|
||||
if (Monitor != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _connectLock.WaitAsync().ConfigureAwait(false);
|
||||
await _connectLock.WaitAsync(token).ConfigureAwait(false);
|
||||
if (Monitor != null)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
var result = await base.StopAsync(token).ConfigureAwait(false);
|
||||
if (Monitor == null)
|
||||
{
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
Logger?.Info($"{DefaultResource.Localizer["ServiceStoped"]}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
var result = await base.StopAsync(token).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -156,7 +158,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
var result = await base.StopAsync(token).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,9 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
return;
|
||||
if (channel.Collects.Count > 0)
|
||||
{
|
||||
var device = channel.Collects.First();
|
||||
if (device.GetType() != GetType())
|
||||
throw new InvalidOperationException("The channel already exists in the device of another type");
|
||||
//var device = channel.Collects.First();
|
||||
//if (device.GetType() != GetType())
|
||||
// throw new InvalidOperationException("The channel already exists in the device of another type");
|
||||
|
||||
if (!SupportMultipleDevice())
|
||||
throw new InvalidOperationException("The proactive response device does not support multiple devices");
|
||||
@@ -97,9 +97,17 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
Channel.Stoping.Add(ChannelStoping);
|
||||
Channel.Started.Add(ChannelStarted);
|
||||
Channel.ChannelReceived.Add(ChannelReceived);
|
||||
|
||||
|
||||
SetChannel();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SetChannel()
|
||||
{
|
||||
Channel.ChannelOptions.MaxConcurrentCount = 1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
~DeviceBase()
|
||||
{
|
||||
@@ -221,6 +229,14 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Channel?.Collects?.Count > 1)
|
||||
{
|
||||
var dataHandlingAdapter = GetDataAdapter();
|
||||
if (adapter.GetType() != dataHandlingAdapter.GetType())
|
||||
{
|
||||
clientChannel.SetDataHandlingAdapter(dataHandlingAdapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,6 +562,17 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
finally
|
||||
{
|
||||
waitLock.Release();
|
||||
if (waitData.WaitResult != null)
|
||||
{
|
||||
if (waitData.WaitResult.Sign != sign)
|
||||
{
|
||||
waitData.WaitResult.Sign = sign;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
waitData.SetResult(new MessageBase() { Sign = sign });
|
||||
}
|
||||
clientChannel.WaitHandlePool.Destroy(waitData);
|
||||
Channel.ChannelReceivedWaitDict.TryRemove(sign, out _);
|
||||
}
|
||||
@@ -944,7 +971,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
|
||||
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
|
||||
{
|
||||
client.WaitHandlePool?.SafeDispose();
|
||||
client.SafeClose();
|
||||
client.Close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.4" />
|
||||
<PackageReference Include="TouchSocket" Version="3.0.25" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="3.0.25" />
|
||||
<PackageReference Include="TouchSocket" Version="3.1.1" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -40,9 +40,7 @@ public class AsyncReadWriteLock
|
||||
|
||||
private void ReleaseWriter()
|
||||
{
|
||||
Interlocked.Decrement(ref _writerCount);
|
||||
|
||||
if (Interlocked.Read(ref _writerCount) == 0)
|
||||
if (Interlocked.Decrement(ref _writerCount) == 0)
|
||||
{
|
||||
var resetEvent = _readerLock;
|
||||
_readerLock = new(false);
|
||||
|
||||
@@ -13,6 +13,7 @@ using BootstrapBlazor.Components;
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using SqlSugar;
|
||||
@@ -139,7 +140,7 @@ public class ControlController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
return await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext.Connection.RemoteIpAddress.MapToIPv4()}", deviceDatas).ConfigureAwait(false);
|
||||
return await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext?.GetRemoteIpAddressToIPv4()}", deviceDatas).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -87,12 +87,12 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
|
||||
// 触发一次设备状态变化和变量值变化事件
|
||||
CollectDevices?.ForEach(a =>
|
||||
{
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
|
||||
});
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline)
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,12 +85,12 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
|
||||
|
||||
CollectDevices?.ForEach(a =>
|
||||
{
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
|
||||
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
|
||||
});
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline)
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
@@ -63,7 +66,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
protected List<TopicJson> GetAlarms(IEnumerable<AlarmModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
List<TopicJson> topicJsonList = new List<TopicJson>();
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -128,7 +131,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
protected List<TopicJson> GetDeviceData(IEnumerable<DevModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
List<TopicJson> topicJsonList = new List<TopicJson>();
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -193,7 +196,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
protected List<TopicJson> GetVariable(IEnumerable<VarModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<VarModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
List<TopicJson> topicJsonList = new List<TopicJson>();
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -267,7 +270,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
data = item;
|
||||
}
|
||||
List<TopicJson> topicJsonList = new List<TopicJson>();
|
||||
var topicJsonList = new List<TopicJson>();
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Count > 0)
|
||||
{
|
||||
@@ -329,6 +332,40 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
return topicJsonList;
|
||||
}
|
||||
|
||||
//protected static byte[] Serialize(object value)
|
||||
//{
|
||||
// var block = new ValueByteBlock(1024 * 64);
|
||||
// try
|
||||
// {
|
||||
// //将数据序列化到内存块
|
||||
// FastBinaryFormatter.Serialize(ref block, value);
|
||||
// block.SeekToStart();
|
||||
// return block.Memory.GetArray().Array;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// block.Dispose();
|
||||
// }
|
||||
//}
|
||||
protected static JsonSerializerOptions NoWriteIndentedJsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = false
|
||||
};
|
||||
protected static JsonSerializerOptions WriteIndentedJsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
protected static byte[] Serialize(object data, bool writeIndented)
|
||||
{
|
||||
if (data == null) return Array.Empty<byte>();
|
||||
byte[] payload = JsonSerializer.SerializeToUtf8Bytes(data, data.GetType(), writeIndented ? WriteIndentedJsonSerializerOptions : NoWriteIndentedJsonSerializerOptions);
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
protected List<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<AlarmModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
@@ -355,7 +392,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
// 上传内容
|
||||
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
|
||||
{
|
||||
var json = Serialize(group.Select(a => a).ToList().ToList());
|
||||
var json = Serialize(group.Select(a => a).ToList().ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json));
|
||||
}
|
||||
@@ -364,7 +401,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = Serialize(gro);
|
||||
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json));
|
||||
}
|
||||
@@ -376,14 +413,14 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
|
||||
{
|
||||
var json = Serialize(data.Select(a => a).ToList());
|
||||
var json = Serialize(data.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = Serialize(group);
|
||||
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json));
|
||||
}
|
||||
}
|
||||
@@ -392,22 +429,6 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
return topicJsonList;
|
||||
}
|
||||
|
||||
protected static ArraySegment<byte> Serialize(object value)
|
||||
{
|
||||
var block = new ValueByteBlock(1024 * 64);
|
||||
try
|
||||
{
|
||||
//将数据序列化到内存块
|
||||
FastBinaryFormatter.Serialize(ref block, value);
|
||||
block.SeekToStart();
|
||||
return block.Memory.GetArray();
|
||||
}
|
||||
finally
|
||||
{
|
||||
block.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected List<TopicArray> GetDeviceTopicArray(IEnumerable<DevModel> item)
|
||||
{
|
||||
IEnumerable<dynamic>? data = Application.DynamicModelExtension.GetDynamicModel<DevModel>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
@@ -436,7 +457,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
|
||||
{
|
||||
// 如果是设备列表,则将整个分组转换为 JSON 字符串
|
||||
var json = Serialize(group.Select(a => a).ToList());
|
||||
var json = Serialize(group.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json));
|
||||
}
|
||||
@@ -445,7 +466,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
// 如果不是设备列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = Serialize(gro);
|
||||
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json));
|
||||
}
|
||||
@@ -458,14 +479,14 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
|
||||
{
|
||||
var json = Serialize(data.Select(a => a).ToList());
|
||||
var json = Serialize(data.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = Serialize(group);
|
||||
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json));
|
||||
}
|
||||
}
|
||||
@@ -501,7 +522,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
|
||||
{
|
||||
// 如果是变量列表,则将整个分组转换为 JSON 字符串
|
||||
var json = Serialize(group.Select(a => a).ToList());
|
||||
var json = Serialize(group.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json));
|
||||
}
|
||||
@@ -510,7 +531,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = Serialize(gro);
|
||||
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json));
|
||||
}
|
||||
@@ -523,14 +544,14 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
|
||||
{
|
||||
var json = Serialize(data.Select(a => a).ToList());
|
||||
var json = Serialize(data.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = Serialize(group);
|
||||
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
|
||||
}
|
||||
}
|
||||
@@ -538,7 +559,6 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
return topicJsonList;
|
||||
}
|
||||
|
||||
|
||||
protected List<TopicArray> GetVariableBasicDataTopicArray(IEnumerable<VariableBasicData> item)
|
||||
{
|
||||
IEnumerable<VariableBasicData>? data = null;
|
||||
@@ -576,7 +596,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
|
||||
{
|
||||
// 如果是变量列表,则将整个分组转换为 JSON 字符串
|
||||
var json = Serialize(group.Select(a => a).ToList());
|
||||
var json = Serialize(group.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json));
|
||||
}
|
||||
@@ -585,7 +605,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = Serialize(gro);
|
||||
var json = Serialize(gro, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
topicJsonList.Add(new(topic, json));
|
||||
}
|
||||
@@ -598,14 +618,14 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
{
|
||||
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
|
||||
{
|
||||
var json = Serialize(data.Select(a => a).ToList());
|
||||
var json = Serialize(data.Select(a => a).ToList(), _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = Serialize(group);
|
||||
var json = Serialize(group, _businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
topicJsonList.Add(new(_businessPropertyWithCacheIntervalScript.VariableTopic, json));
|
||||
}
|
||||
}
|
||||
@@ -614,5 +634,9 @@ public abstract partial class BusinessBaseWithCacheIntervalScript<VarModel, DevM
|
||||
}
|
||||
|
||||
|
||||
protected string GetString(string topic, byte[] json, int count)
|
||||
{
|
||||
return $"Topic:{topic}{Environment.NewLine}PayLoad:{Encoding.UTF8.GetString(json)} {Environment.NewLine} VarModelQueue:{count}";
|
||||
}
|
||||
#endregion 封装方法
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
|
||||
// 触发一次变量值变化事件
|
||||
IdVariableRuntimes.ForEach(a =>
|
||||
{
|
||||
if (a.Value.IsOnline)
|
||||
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
|
||||
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class BusinessPropertyWithCacheIntervalDBScript : BusinessPropertyWithCacheInterval
|
||||
{
|
||||
/// <summary>
|
||||
/// 表定义实体脚本
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 3)]
|
||||
public string? BigTextScriptTableModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据脚本
|
||||
/// </summary>
|
||||
[AutoGenerateColumn(ComponentType = typeof(Textarea), Rows = 3)]
|
||||
public string? BigTextScriptDataModel { get; set; }
|
||||
}
|
||||
@@ -12,11 +12,12 @@ namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public struct TopicArray
|
||||
{
|
||||
public TopicArray(string topic, ArraySegment<byte> json)
|
||||
public TopicArray(string topic, byte[] json)
|
||||
{
|
||||
Topic = topic; Json = json;
|
||||
}
|
||||
|
||||
public ArraySegment<byte> Json { get; set; }
|
||||
public byte[] Json { get; set; }
|
||||
public string Topic { get; set; }
|
||||
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
|
||||
/// <summary>
|
||||
/// 缓存超时
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "缓存超时")]
|
||||
[SugarColumn(ColumnDescription = "缓存超时", IsNullable = true, DefaultValue = "500")]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
[MinValue(100)]
|
||||
public override int CacheTimeout { get; set; } = 500;
|
||||
@@ -148,7 +148,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
|
||||
/// <summary>
|
||||
/// 连接超时
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "连接超时")]
|
||||
[SugarColumn(ColumnDescription = "连接超时", IsNullable = true, DefaultValue = "3000")]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
[MinValue(100)]
|
||||
public override ushort ConnectTimeout { get; set; } = 3000;
|
||||
@@ -156,33 +156,36 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
|
||||
/// <summary>
|
||||
/// 最大并发数
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "最大并发数")]
|
||||
[SugarColumn(ColumnDescription = "最大并发数", IsNullable = true, DefaultValue = "1")]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
[MinValue(1)]
|
||||
public override int MaxConcurrentCount { get; set; } = 1;
|
||||
|
||||
[SugarColumn(ColumnDescription = "最大连接数")]
|
||||
[SugarColumn(ColumnDescription = "最大连接数", IsNullable = true, DefaultValue = "10000")]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
public override int MaxClientCount { get; set; } = 10000;
|
||||
[SugarColumn(ColumnDescription = "客户端滑动过期时间")]
|
||||
|
||||
[SugarColumn(ColumnDescription = "客户端滑动过期时间", IsNullable = true, DefaultValue = "120000")]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
public override int CheckClearTime { get; set; } = 120000;
|
||||
|
||||
[SugarColumn(ColumnDescription = "心跳内容", IsNullable = true)]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
public override string Heartbeat { get; set; } = "Heartbeat";
|
||||
|
||||
#region dtu终端
|
||||
|
||||
[SugarColumn(ColumnDescription = "心跳间隔")]
|
||||
[SugarColumn(ColumnDescription = "心跳间隔", IsNullable = true, DefaultValue = "60000")]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
public override int HeartbeatTime { get; set; } = 60000;
|
||||
|
||||
[SugarColumn(ColumnDescription = "DtuId", IsNullable = true)]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
public override string DtuId { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
[SugarColumn(ColumnDescription = "Dtu类型")]
|
||||
[SugarColumn(ColumnDescription = "Dtu类型", IsNullable = true, DefaultValue = "0")]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
public override DtuSeviceType DtuSeviceType { get; set; }
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class Device : BaseDataEntity, IValidatableObject
|
||||
/// <summary>
|
||||
/// 通道
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "通道", Length = 200)]
|
||||
[SugarColumn(ColumnDescription = "通道")]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
[IgnoreExcel]
|
||||
[MinValue(1)]
|
||||
|
||||
@@ -51,6 +51,13 @@ public class Variable : BaseDataEntity, IValidatableObject
|
||||
[Required]
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 采集组
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "采集组", IsNullable = true)]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 1)]
|
||||
public virtual string CollectGroup { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 分组名称
|
||||
/// </summary>
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
"Name": "Name",
|
||||
"Description": "Description",
|
||||
"Group": "Group",
|
||||
"CollectGroup": "CollectGroup",
|
||||
"DeviceId": "CollectionDevice",
|
||||
"DeviceId.MinValue": "{0} cannot be empty",
|
||||
"DeviceId.Required": "{0} cannot be empty",
|
||||
@@ -452,6 +453,7 @@
|
||||
"Name.Required": "{0} cannot be empty",
|
||||
"Description": "Description",
|
||||
"Group": "Group",
|
||||
"CollectGroup": "CollectGroup",
|
||||
"DeviceId": "CollectionDevice",
|
||||
"DeviceId.MinValue": "{0} cannot be empty",
|
||||
"DeviceId.Required": "{0} cannot be empty",
|
||||
|
||||
@@ -89,7 +89,8 @@
|
||||
"Name": "名称",
|
||||
"Name.Required": " {0} 不可为空",
|
||||
"Description": "描述",
|
||||
"Group": "分组",
|
||||
"Group": "业务组",
|
||||
"CollectGroup": "采集组",
|
||||
"DeviceId": "采集设备",
|
||||
"DeviceId.MinValue": " {0} 不可为空",
|
||||
"DeviceId.Required": " {0} 不可为空",
|
||||
@@ -486,7 +487,8 @@
|
||||
"Name": "名称",
|
||||
"Name.Required": " {0} 不可为空",
|
||||
"Description": "描述",
|
||||
"Group": "分组",
|
||||
"Group": "业务组",
|
||||
"CollectGroup": "采集组",
|
||||
"DeviceId": "采集设备",
|
||||
"DeviceId.MinValue": " {0} 不可为空",
|
||||
"DeviceId.Required": " {0} 不可为空",
|
||||
|
||||
@@ -131,7 +131,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Config?.SafeDispose();
|
||||
//Config?.SafeDispose();
|
||||
|
||||
GlobalData.Channels.TryRemove(Id, out _);
|
||||
DeviceThreadManage = null;
|
||||
@@ -146,4 +146,57 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
|
||||
return $"{Name}[{base.ToString()}]";
|
||||
}
|
||||
|
||||
|
||||
public IChannel GetChannel(TouchSocketConfig config)
|
||||
{
|
||||
lock (GlobalData.Channels)
|
||||
{
|
||||
|
||||
if (DeviceThreadManage?.Channel?.DisposedValue == false)
|
||||
return DeviceThreadManage?.Channel;
|
||||
|
||||
|
||||
if (ChannelType == ChannelTypeEnum.TcpService
|
||||
|| ChannelType == ChannelTypeEnum.SerialPort
|
||||
|| ChannelType == ChannelTypeEnum.UdpSession
|
||||
)
|
||||
{
|
||||
//获取相同配置的Tcp服务或Udp服务或COM
|
||||
var same = GlobalData.Channels.FirstOrDefault(a =>
|
||||
{
|
||||
if (a.Value == this)
|
||||
return false;
|
||||
if (a.Value.DeviceThreadManage?.Channel?.DisposedValue == true || a.Value.DeviceThreadManage?.Channel?.DisposedValue == null)
|
||||
return false;
|
||||
|
||||
if (a.Value.ChannelType == ChannelType)
|
||||
{
|
||||
if (a.Value.ChannelType == ChannelTypeEnum.TcpService)
|
||||
if (a.Value.BindUrl == BindUrl)
|
||||
return true;
|
||||
if (a.Value.ChannelType == ChannelTypeEnum.UdpSession)
|
||||
if ((!BindUrl.IsNullOrWhiteSpace()) && a.Value.BindUrl == BindUrl)
|
||||
return true;
|
||||
if (a.Value.ChannelType == ChannelTypeEnum.SerialPort)
|
||||
if (a.Value.PortName == PortName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}).Value;
|
||||
|
||||
if (same != null)
|
||||
{
|
||||
return same.GetChannel(config);
|
||||
}
|
||||
}
|
||||
|
||||
if (DeviceThreadManage?.Channel?.DisposedValue == false)
|
||||
return DeviceThreadManage?.Channel;
|
||||
|
||||
var ichannel = config.GetChannel(this);
|
||||
return ichannel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -83,10 +83,15 @@ public class VariableBasicData : IPrimaryIdEntity
|
||||
/// <inheritdoc cref="Variable.Name"/>
|
||||
public string Name { get; set; }
|
||||
/// <inheritdoc cref="Variable.Group"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Group { get; set; }
|
||||
/// <inheritdoc cref="VariableRuntime.DeviceName"/>
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.RuntimeType"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string RuntimeType { get; set; }
|
||||
/// <inheritdoc cref="VariableRuntime.Value"/>
|
||||
public object Value { get; set; }
|
||||
/// <inheritdoc cref="VariableRuntime.RawValue"/>
|
||||
|
||||
@@ -19,5 +19,8 @@ public class VariableMapper : IRegister
|
||||
{
|
||||
config.ForType<Variable, VariableRuntime>()
|
||||
.Map(dest => dest.Value, src => src.InitValue);
|
||||
|
||||
config.ForType<VariableRuntime, VariableRuntime>()
|
||||
.Ignore(dest => dest.DeviceRuntime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,16 +216,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
|
||||
await RuntimeServiceHelper.InitAsync(newChannelRuntimes, newDeviceRuntimes, _logger).ConfigureAwait(false);
|
||||
|
||||
|
||||
var startCollectChannelEnable = GlobalData.StartCollectChannelEnable;
|
||||
var startBusinessChannelEnable = GlobalData.StartBusinessChannelEnable;
|
||||
|
||||
var collectChannelRuntimes = newChannelRuntimes.Where(x => (x.Enable && x.IsCollect == true && startCollectChannelEnable));
|
||||
|
||||
var businessChannelRuntimes = newChannelRuntimes.Where(x => (x.Enable && x.IsCollect == false && startBusinessChannelEnable));
|
||||
|
||||
//根据初始冗余属性,筛选启动
|
||||
await GlobalData.ChannelThreadManage.RestartChannelAsync(businessChannelRuntimes).ConfigureAwait(false);
|
||||
await GlobalData.ChannelThreadManage.RestartChannelAsync(collectChannelRuntimes).ConfigureAwait(false);
|
||||
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -329,8 +329,8 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
ManageHelper.CheckChannelCount(insertData.Count);
|
||||
|
||||
using var db = GetDB();
|
||||
await db.Fastest<Channel>().PageSize(100000).BulkCopyAsync(insertData).ConfigureAwait(false);
|
||||
await db.Fastest<Channel>().PageSize(100000).BulkUpdateAsync(upData).ConfigureAwait(false);
|
||||
await db.BulkCopyAsync(insertData, 100000).ConfigureAwait(false);
|
||||
await db.BulkUpdateAsync(upData, 100000).ConfigureAwait(false);
|
||||
DeleteChannelFromCache();
|
||||
return channels.Select(a => a.Id).ToHashSet();
|
||||
}
|
||||
|
||||
@@ -351,8 +351,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
|
||||
ManageHelper.CheckDeviceCount(insertData.Count);
|
||||
|
||||
using var db = GetDB();
|
||||
await db.Fastest<Device>().PageSize(100000).BulkCopyAsync(insertData).ConfigureAwait(false);
|
||||
await db.Fastest<Device>().PageSize(100000).BulkUpdateAsync(upData).ConfigureAwait(false);
|
||||
await db.BulkCopyAsync(insertData, 100000).ConfigureAwait(false);
|
||||
await db.BulkUpdateAsync(upData, 100000).ConfigureAwait(false);
|
||||
DeleteDeviceFromCache();
|
||||
return devices.Select(a => a.Id).ToHashSet();
|
||||
}
|
||||
@@ -379,9 +379,6 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
|
||||
// 设备页的导入预览对象
|
||||
ImportPreviewOutput<Device> deviceImportPreview = new();
|
||||
|
||||
// 获取所有驱动程序,并将驱动程序的完整名称作为键构建字典
|
||||
var driverPluginFullNameDict = _pluginService.GetList().ToDictionary(a => a.FullName);
|
||||
|
||||
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
|
||||
var driverPluginNameDict = _pluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
|
||||
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
|
||||
|
||||
@@ -16,8 +16,6 @@ using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
internal sealed class ChannelThreadManage : IChannelThreadManage
|
||||
@@ -115,8 +113,6 @@ internal sealed class ChannelThreadManage : IChannelThreadManage
|
||||
{
|
||||
await PrivateRemoveChannelsAsync(channelRuntimes.Select(a => a.Id)).ConfigureAwait(false);
|
||||
|
||||
BytePool.Default.Clear();
|
||||
|
||||
await channelRuntimes.ParallelForEachAsync(async (channelRuntime, token) =>
|
||||
{
|
||||
try
|
||||
|
||||
@@ -117,10 +117,9 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
// 添加默认日志记录器
|
||||
LogMessage.AddLogger(new EasyLogger(logger.Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
|
||||
var ichannel = config.GetChannel(channelRuntime);
|
||||
|
||||
// 根据配置获取通道实例
|
||||
Channel = ichannel;
|
||||
Channel = channelRuntime.GetChannel(config);
|
||||
|
||||
|
||||
//初始设置输出文本日志
|
||||
SetLog(CurrentChannel.LogLevel);
|
||||
@@ -901,6 +900,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
await NewDeviceLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
await PrivateRemoveDevicesAsync(Drivers.Keys).ConfigureAwait(false);
|
||||
if (Channel?.Collects.Count == 0)
|
||||
Channel?.SafeDispose();
|
||||
|
||||
LogMessage?.LogInformation(Localizer["ChannelDispose", CurrentChannel?.Name ?? string.Empty]);
|
||||
|
||||
@@ -71,16 +71,7 @@ internal sealed class GatewayMonitorHostedService : BackgroundService, IGatewayM
|
||||
}
|
||||
}
|
||||
|
||||
var startCollectChannelEnable = GlobalData.StartCollectChannelEnable;
|
||||
var startBusinessChannelEnable = GlobalData.StartBusinessChannelEnable;
|
||||
|
||||
var collectChannelRuntimes = channelRuntimes.Where(x => (x.Enable && x.IsCollect == true && startCollectChannelEnable));
|
||||
|
||||
var businessChannelRuntimes = channelRuntimes.Where(x => (x.Enable && x.IsCollect == false && startBusinessChannelEnable));
|
||||
|
||||
//根据初始冗余属性,筛选启动
|
||||
await ChannelThreadManage.RestartChannelAsync(businessChannelRuntimes).ConfigureAwait(false);
|
||||
await ChannelThreadManage.RestartChannelAsync(collectChannelRuntimes).ConfigureAwait(false);
|
||||
await ChannelThreadManage.RestartChannelAsync(channelRuntimes).ConfigureAwait(false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -310,7 +310,6 @@ internal static class RuntimeServiceHelper
|
||||
}
|
||||
}
|
||||
|
||||
item.Value.Dispose();
|
||||
}
|
||||
if (group.Key != null)
|
||||
{
|
||||
@@ -321,6 +320,19 @@ internal static class RuntimeServiceHelper
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void VariableRuntimesDispose(IEnumerable<long> variableIds)
|
||||
{
|
||||
|
||||
foreach (var variableId in variableIds)
|
||||
{
|
||||
|
||||
if (GlobalData.IdVariables.TryGetValue(variableId, out var variableRuntime))
|
||||
{
|
||||
variableRuntime.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void AddCollectChangedDriver(IEnumerable<VariableRuntime> newVariableRuntimes, ConcurrentHashSet<IDriver> changedDriver)
|
||||
|
||||
@@ -43,10 +43,10 @@ public class VariableRuntimeService : IVariableRuntimeService
|
||||
//获取变量,先找到原插件线程,然后修改插件线程内的字典,再改动全局字典,最后刷新插件
|
||||
|
||||
ConcurrentHashSet<IDriver> changedDriver = new();
|
||||
|
||||
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
|
||||
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
|
||||
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
|
||||
|
||||
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
|
||||
|
||||
if (restart)
|
||||
{
|
||||
@@ -79,9 +79,9 @@ public class VariableRuntimeService : IVariableRuntimeService
|
||||
|
||||
ConcurrentHashSet<IDriver> changedDriver = new();
|
||||
|
||||
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
|
||||
|
||||
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
|
||||
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
|
||||
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
|
||||
|
||||
if (restart)
|
||||
{
|
||||
@@ -111,6 +111,7 @@ public class VariableRuntimeService : IVariableRuntimeService
|
||||
ConcurrentHashSet<IDriver> changedDriver = new();
|
||||
|
||||
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
|
||||
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
|
||||
|
||||
if (restart)
|
||||
{
|
||||
@@ -144,10 +145,9 @@ public class VariableRuntimeService : IVariableRuntimeService
|
||||
var variableIds = newVariableRuntimes.Select(a => a.Id).ToHashSet();
|
||||
|
||||
ConcurrentHashSet<IDriver> changedDriver = new();
|
||||
|
||||
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
|
||||
|
||||
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
|
||||
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
|
||||
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
|
||||
|
||||
if (restart)
|
||||
{
|
||||
@@ -237,10 +237,9 @@ public class VariableRuntimeService : IVariableRuntimeService
|
||||
|
||||
ConcurrentHashSet<IDriver> changedDriver = new();
|
||||
|
||||
|
||||
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
|
||||
|
||||
RuntimeServiceHelper.VariableRuntimesDispose(variableIds);
|
||||
RuntimeServiceHelper.AddCollectChangedDriver(newVariableRuntimes, changedDriver);
|
||||
RuntimeServiceHelper.AddBusinessChangedDriver(variableIds, changedDriver);
|
||||
|
||||
if (restart)
|
||||
{
|
||||
|
||||
@@ -217,9 +217,9 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
|
||||
var result = await db.UseTranAsync(async () =>
|
||||
{
|
||||
await db.Fastest<Channel>().PageSize(100000).BulkCopyAsync(newChannels).ConfigureAwait(false);
|
||||
await db.Fastest<Device>().PageSize(100000).BulkCopyAsync(newDevices).ConfigureAwait(false);
|
||||
await db.Fastest<Variable>().PageSize(100000).BulkCopyAsync(newVariables).ConfigureAwait(false);
|
||||
await db.BulkCopyAsync(newChannels, 100000).ConfigureAwait(false);
|
||||
await db.BulkCopyAsync(newDevices, 100000).ConfigureAwait(false);
|
||||
await db.BulkCopyAsync(newVariables, 100000).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
@@ -486,8 +486,8 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
var insertData = variables.Where(a => !a.IsUp).ToList();
|
||||
ManageHelper.CheckVariableCount(insertData.Count);
|
||||
using var db = GetDB();
|
||||
await db.Fastest<Variable>().PageSize(100000).BulkCopyAsync(insertData).ConfigureAwait(false);
|
||||
await db.Fastest<Variable>().PageSize(100000).BulkUpdateAsync(upData).ConfigureAwait(false);
|
||||
await db.BulkCopyAsync(insertData, 100000).ConfigureAwait(false);
|
||||
await db.BulkUpdateAsync(upData, 100000).ConfigureAwait(false);
|
||||
_dispatchService.Dispatch(new());
|
||||
DeleteVariableCache();
|
||||
return variables.Select(a => a.Id).ToHashSet();
|
||||
@@ -555,8 +555,6 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
// 设备页导入预览输出
|
||||
ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview = new();
|
||||
|
||||
// 获取驱动插件的全名和名称的字典
|
||||
var driverPluginFullNameDict = _pluginService.GetList().ToDictionary(a => a.FullName);
|
||||
var driverPluginNameDict = _pluginService.GetList().ToDictionary(a => a.Name);
|
||||
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
@@ -17,7 +21,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
[AppStartup(-100)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureAdminApp(IServiceCollection services)
|
||||
public void Configure(IServiceCollection services)
|
||||
{
|
||||
services.AddConfigurableOptions<ChannelThreadOptions>();
|
||||
services.AddConfigurableOptions<GatewayLogOptions>();
|
||||
@@ -62,8 +66,9 @@ public class Startup : AppStartup
|
||||
services.AddGatewayHostedService<IGatewayMonitorHostedService, GatewayMonitorHostedService>();
|
||||
}
|
||||
|
||||
public void UseAdminCore(IServiceProvider serviceProvider)
|
||||
public void Use(IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
var serviceProvider = applicationBuilder.ApplicationServices;
|
||||
//检查ConfigId
|
||||
var configIdGroup = DbContext.DbConfigs.GroupBy(it => it.ConfigId);
|
||||
foreach (var configId in configIdGroup)
|
||||
@@ -71,6 +76,7 @@ public class Startup : AppStartup
|
||||
if (configId.Count() > 1) throw new($"Sqlsugar connect configId: {configId.Key} Duplicate!");
|
||||
}
|
||||
|
||||
|
||||
//遍历配置
|
||||
DbContext.DbConfigs?.ForEach(it =>
|
||||
{
|
||||
@@ -79,7 +85,55 @@ public class Startup : AppStartup
|
||||
if (it.InitTable == true)
|
||||
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
|
||||
});
|
||||
|
||||
|
||||
//兼容变量名称唯一键处理
|
||||
try
|
||||
{
|
||||
using var db = DbContext.GetDB<Variable>();
|
||||
if (db.DbMaintenance.IsAnyIndex("unique_variable_name"))
|
||||
{
|
||||
var tables = db.DbMaintenance.DropIndex("unique_variable_name");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
|
||||
var fullName = Assembly.GetExecutingAssembly().FullName;//获取程序集全名
|
||||
CodeFirstUtils.CodeFirst(fullName!);//CodeFirst
|
||||
|
||||
|
||||
|
||||
//10.4.9 删除logenable
|
||||
try
|
||||
{
|
||||
using var db = DbContext.GetDB<Channel>();
|
||||
if (db.DbMaintenance.IsAnyColumn(nameof(Channel), "LogEnable", false))
|
||||
{
|
||||
var tables = db.DbMaintenance.DropColumn(nameof(Channel), "LogEnable");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
try
|
||||
{
|
||||
using var db = DbContext.GetDB<Device>();
|
||||
if (db.DbMaintenance.IsAnyColumn(nameof(Device), "LogEnable", false))
|
||||
{
|
||||
var tables = db.DbMaintenance.DropColumn(nameof(Device), "LogEnable");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
|
||||
|
||||
|
||||
serviceProvider.GetService<IHostApplicationLifetime>().ApplicationStarted.Register(() =>
|
||||
{
|
||||
serviceProvider.GetService<ILoggerFactory>().CreateLogger(nameof(ThingsGateway)).LogInformation("ThingsGateway is started...");
|
||||
});
|
||||
serviceProvider.GetService<IHostApplicationLifetime>().ApplicationStopping.Register(() =>
|
||||
{
|
||||
serviceProvider.GetService<ILoggerFactory>().CreateLogger(nameof(ThingsGateway)).LogInformation("ThingsGateway is stopping...");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="SqlSugar.TDengineCore" Version="4.18.6" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.25" />
|
||||
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.0.25" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.1" />
|
||||
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.1" />
|
||||
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
@@ -105,8 +105,8 @@ public class TcpSessionClientDto
|
||||
public string PluginInfos { get; set; }
|
||||
|
||||
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
|
||||
public DateTime LastReceivedTime { get; set; }
|
||||
public DateTimeOffset LastReceivedTime { get; set; }
|
||||
|
||||
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
|
||||
public DateTime LastSentTime { get; set; }
|
||||
public DateTimeOffset LastSentTime { get; set; }
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
ShowExportButton
|
||||
ShowDefaultButtons=true
|
||||
ShowSearch=false
|
||||
ShowExtendEditButton="true"
|
||||
ShowExtendDeleteButton="true"
|
||||
ExtendButtonColumnWidth=220
|
||||
OnSaveAsync="Save"
|
||||
OnDeleteAsync="Delete"
|
||||
|
||||
@@ -1299,6 +1299,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
try
|
||||
{
|
||||
if (Disposed) return;
|
||||
await Task.Delay(1000);
|
||||
await OnClickSearch(SearchText);
|
||||
|
||||
Value = GetValue(Value);
|
||||
@@ -1310,7 +1311,6 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
_isExecuting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
ShowExportButton
|
||||
ShowDefaultButtons=true
|
||||
ShowSearch=false
|
||||
ShowExtendEditButton="true"
|
||||
ShowExtendDeleteButton="true"
|
||||
ExtendButtonColumnWidth=220
|
||||
OnSaveAsync="Save"
|
||||
OnDeleteAsync="Delete"
|
||||
|
||||
@@ -129,7 +129,6 @@ public partial class PropertyComponent : IPropertyUIBase
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var op = new DialogOption()
|
||||
{
|
||||
IsScrolling = true,
|
||||
@@ -144,6 +143,147 @@ public partial class PropertyComponent : IPropertyUIBase
|
||||
{
|
||||
{nameof(ScriptCheck.Data),data },
|
||||
{nameof(ScriptCheck.Script),script },
|
||||
{nameof(ScriptCheck.OnGetDemo),()=>
|
||||
{
|
||||
return
|
||||
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel)?
|
||||
"""
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using System.Dynamic;
|
||||
using TouchSocket.Core;
|
||||
public class S1 : IDynamicModel
|
||||
{
|
||||
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
|
||||
{
|
||||
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
|
||||
foreach (var v in datas)
|
||||
{
|
||||
var device = (DeviceBasicData)v;
|
||||
var expando = new ExpandoObject();
|
||||
var deviceObj = new ExpandoObject();
|
||||
|
||||
deviceObj.TryAdd(nameof(Device.Description), device.Description);
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.ActiveTime), device.ActiveTime);
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.DeviceStatus), device.DeviceStatus.ToString());
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.LastErrorMessage), device.LastErrorMessage);
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.PluginName), device.PluginName);
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.Remark1), device.Remark1);
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.Remark2), device.Remark2);
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.Remark3), device.Remark3);
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.Remark4), device.Remark4);
|
||||
deviceObj.TryAdd(nameof(DeviceBasicData.Remark5), device.Remark5);
|
||||
|
||||
|
||||
expando.TryAdd(nameof(Device.Name), deviceObj);
|
||||
|
||||
}
|
||||
return deviceObjs;
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
:
|
||||
|
||||
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptVariableModel)?
|
||||
|
||||
"""
|
||||
using System.Dynamic;
|
||||
using ThingsGateway.Foundation;
|
||||
using TouchSocket.Core;
|
||||
public class S2 : IDynamicModel
|
||||
{
|
||||
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
|
||||
{
|
||||
|
||||
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
|
||||
//按设备名称分组
|
||||
var groups = datas.Where(a => !string.IsNullOrEmpty(((VariableBasicData)a).DeviceName)).GroupBy(a => ((VariableBasicData)a).DeviceName, a => ((VariableBasicData)a));
|
||||
foreach (var group in groups)
|
||||
{
|
||||
//按采集时间分组
|
||||
var data = group.GroupBy(a => a.CollectTime.DateTimeToUnixTimestamp());
|
||||
var deviceObj = new ExpandoObject();
|
||||
List<ExpandoObject> expandos = new List<ExpandoObject>();
|
||||
foreach (var item in data)
|
||||
{
|
||||
var expando = new ExpandoObject();
|
||||
expando.TryAdd("ts", item.Key);
|
||||
var variableObj = new ExpandoObject();
|
||||
foreach (var tag in item)
|
||||
{
|
||||
variableObj.TryAdd(tag.Name, tag.Value);
|
||||
}
|
||||
expando.TryAdd("values", variableObj);
|
||||
|
||||
expandos.Add(expando);
|
||||
}
|
||||
deviceObj.TryAdd(group.Key, expandos);
|
||||
deviceObjs.Add(deviceObj);
|
||||
}
|
||||
|
||||
return deviceObjs;
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
:
|
||||
|
||||
pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel)?
|
||||
|
||||
"""
|
||||
using System.Dynamic;
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using TouchSocket.Core;
|
||||
public class DeviceScript : IDynamicModel
|
||||
{
|
||||
public IEnumerable<dynamic> GetList(IEnumerable<object> datas)
|
||||
{
|
||||
|
||||
List<ExpandoObject> deviceObjs = new List<ExpandoObject>();
|
||||
//按设备名称分组
|
||||
var groups = datas.Where(a => !string.IsNullOrEmpty(((AlarmVariable)a).DeviceName)).GroupBy(a => ((AlarmVariable)a).DeviceName, a => ((AlarmVariable)a));
|
||||
foreach (var group in groups)
|
||||
{
|
||||
//按采集时间分组
|
||||
var data = group.GroupBy(a => a.AlarmTime.DateTimeToUnixTimestamp());
|
||||
var deviceObj = new ExpandoObject();
|
||||
List<ExpandoObject> expandos = new List<ExpandoObject>();
|
||||
foreach (var item in data)
|
||||
{
|
||||
var expando = new ExpandoObject();
|
||||
expando.TryAdd("ts", item.Key);
|
||||
var variableObj = new ExpandoObject();
|
||||
foreach (var tag in item)
|
||||
{
|
||||
var alarmObj = new ExpandoObject();
|
||||
alarmObj.TryAdd(nameof(tag.AlarmCode), tag.AlarmCode);
|
||||
alarmObj.TryAdd(nameof(tag.AlarmText), tag.AlarmText);
|
||||
alarmObj.TryAdd(nameof(tag.AlarmType), tag.AlarmType);
|
||||
alarmObj.TryAdd(nameof(tag.AlarmLimit), tag.AlarmLimit);
|
||||
alarmObj.TryAdd(nameof(tag.EventTime), tag.EventTime);
|
||||
alarmObj.TryAdd(nameof(tag.EventType), tag.EventType);
|
||||
|
||||
variableObj.TryAdd(tag.Name, alarmObj);
|
||||
}
|
||||
expando.TryAdd("alarms", variableObj);
|
||||
|
||||
expandos.Add(expando);
|
||||
}
|
||||
deviceObj.TryAdd(group.Key, expandos);
|
||||
deviceObjs.Add(deviceObj);
|
||||
}
|
||||
|
||||
return deviceObjs;
|
||||
}
|
||||
}
|
||||
"""
|
||||
:
|
||||
""
|
||||
;
|
||||
}
|
||||
},
|
||||
{nameof(ScriptCheck.ScriptChanged),EventCallback.Factory.Create<string>(this, v =>
|
||||
{
|
||||
if (pname == nameof(BusinessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel))
|
||||
@@ -171,3 +311,7 @@ public partial class PropertyComponent : IPropertyUIBase
|
||||
[Inject]
|
||||
private DialogService DialogService { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("获取变量类实体,可用方法 <code>GlobalData.GetVariable(\"设备名称1\",\"变量名称1\")</code> "))</Alert>
|
||||
<Alert Icon="fa-solid fa-circle-check" Color="Color.Success">@(new MarkupString("详细说明查看文档对应内容页面"))</Alert>
|
||||
|
||||
<Button IsAsync OnClick="GetDemo" class="mt-3" Text="Demo" />
|
||||
|
||||
</div>
|
||||
<div class="col-6 col-md-6">
|
||||
<BootstrapLabel Value=@Localizer["Input"] ShowLabelTooltip="true" />
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
@@ -56,4 +58,13 @@ public partial class ScriptCheck
|
||||
}
|
||||
[Inject]
|
||||
private IStringLocalizer<DeviceEditComponent> Localizer { get; set; }
|
||||
|
||||
private async Task GetDemo(Microsoft.AspNetCore.Components.Web.MouseEventArgs args)
|
||||
{
|
||||
Script = OnGetDemo?.Invoke();
|
||||
await Change(Script);
|
||||
}
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public Func<string> OnGetDemo { get; set; }
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<EditorItem @bind-Field="@context.Name" Readonly=BatchEditEnable />
|
||||
|
||||
<EditorItem @bind-Field="@context.Description" />
|
||||
<EditorItem @bind-Field="@context.CollectGroup" />
|
||||
<EditorItem @bind-Field="@context.Group" />
|
||||
|
||||
<EditorItem @bind-Field="@context.Unit" />
|
||||
|
||||
@@ -212,8 +212,8 @@ public partial class VariableEditComponent
|
||||
{
|
||||
var component = new BootstrapDynamicComponent(data.VariablePropertyUIType, new Dictionary<string, object?>
|
||||
{
|
||||
[nameof(VariableEditComponent.Model)] = Model,
|
||||
[nameof(DeviceEditComponent.PluginPropertyEditorItems)] = data.EditorItems,
|
||||
[nameof(IPropertyUIBase.Model)] = Model,
|
||||
[nameof(IPropertyUIBase.PluginPropertyEditorItems)] = data.EditorItems,
|
||||
});
|
||||
VariablePropertyRenderFragments.AddOrUpdate(id, component.Render());
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
ShowExportButton
|
||||
ShowDefaultButtons=true
|
||||
ShowSearch=false
|
||||
ShowExtendEditButton="true"
|
||||
ShowExtendDeleteButton="true"
|
||||
ExtendButtonColumnWidth=220
|
||||
OnSaveAsync="Save"
|
||||
OnDeleteAsync="Delete"
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ThingsGateway.Gateway.Razor;
|
||||
[AppStartup(-1000)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureAdminApp(IServiceCollection services)
|
||||
public void Configure(IServiceCollection services)
|
||||
{
|
||||
services.AddBootstrapBlazorWinBoxService();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" />
|
||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.3" />
|
||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.4" />
|
||||
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
|
||||
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.1" />
|
||||
<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" />
|
||||
|
||||
3
src/Gateway/ThingsGateway.Management/FodyWeavers.xml
Normal file
3
src/Gateway/ThingsGateway.Management/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Rougamo />
|
||||
</Weavers>
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
|
||||
"ThingsGateway.Management.RedundancyService": {
|
||||
"EditRedundancyOption": "EditRedundancyOption"
|
||||
},
|
||||
"ThingsGateway.Management.Authentication": {
|
||||
"UUID": "UUID",
|
||||
"RegisterStatus": "RegisterStatus",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"ThingsGateway.Management.RedundancyService": {
|
||||
"EditRedundancyOption": "修改网关冗余配置"
|
||||
},
|
||||
"ThingsGateway.Management.Authentication": {
|
||||
"UUID": "唯一编码",
|
||||
"RegisterStatus": "注册状态",
|
||||
|
||||
@@ -15,7 +15,7 @@ using TouchSocket.Rpc;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
public partial class ReverseCallbackServer : RpcServer
|
||||
public partial class ReverseCallbackServer : SingletonRpcServer
|
||||
{
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public void UpdateGatewayData(List<DeviceDataWithValue> deviceDatas)
|
||||
|
||||
@@ -4,13 +4,20 @@
|
||||
@if (WebsiteOption.Value.ShowAuthorize)
|
||||
{
|
||||
|
||||
<Card class="mb-2">
|
||||
<Card class="h-100">
|
||||
<BodyTemplate>
|
||||
<div class="ma-1">
|
||||
<span>@Localizer["UUID"]</span>
|
||||
<div class="my-2 ms-4 text-truncate">
|
||||
<div class="row g-3 form-inline">
|
||||
<div class="col-12 col-sm-12">
|
||||
|
||||
<label class="form-label">
|
||||
|
||||
@Localizer["UUID"]
|
||||
</label>
|
||||
|
||||
<Textarea Value="@ProAuthentication.UUID" rows="5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PopConfirmButton Size="Size.Small" Color="Color.Success" Icon="fa-solid fa-bars" Text="@Localizer["Register"]" IsAsync OnConfirm="Register">
|
||||
|
||||
@@ -22,7 +29,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="ma-1">
|
||||
<div class="ma-1 mt-4">
|
||||
|
||||
<div class="row g-3 form-inline">
|
||||
<div class="col-12 col-sm-12">
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
@namespace ThingsGateway.Management
|
||||
|
||||
|
||||
<div class=@($"{ClassString} row my-2 mx-2") style="min-height:500px;height: 50%;">
|
||||
<div class=@($"{ClassString} row g-0 h-100 mx-2") style="min-height:500px;">
|
||||
|
||||
<div class="col-12 h-100">
|
||||
<div class="col-12 col-md-6 h-100">
|
||||
|
||||
<EditComponent Model="Model" OnSave="OnSaveRedundancy"/>
|
||||
<EditComponent ItemsPerRow=1 Model="Model" OnSave="OnSaveRedundancy" />
|
||||
|
||||
</div>
|
||||
<div class="col-12 ">
|
||||
<div class="col-12 col-md-6 h-100">
|
||||
|
||||
@if (Logger != null)
|
||||
{
|
||||
<LogConsole HeightString="500px" LogLevel=@(Logger.LogLevel) LogLevelChanged="(a)=>{
|
||||
<LogConsole LogLevel=@(Logger.LogLevel) LogLevelChanged="(a)=>{
|
||||
Logger.LogLevel=a;
|
||||
}" LogPath=@LogPath HeaderText=@HeaderText></LogConsole>
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<TabItem Text=@RedundancyLocalizer[nameof(RedundancyOptions)]>
|
||||
|
||||
<Card class="mb-2">
|
||||
<Card class="h-100">
|
||||
|
||||
<HeaderTemplate>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
<TabItem Text=@ManagementLocalizer["Restart"]>
|
||||
|
||||
<Card class="mb-2">
|
||||
<Card class="h-100">
|
||||
|
||||
<BodyTemplate>
|
||||
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
|
||||
<div class="p-1">
|
||||
<Button IsAsync Color="Color.Primary" OnClick="OnUpdate">@ManagementLocalizer["Update"]</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
@@ -36,9 +36,8 @@ public partial class UpdateZipFilePage
|
||||
var data = await UpdateZipFileHostedService.GetList();
|
||||
return new QueryData<UpdateZipFile>() { Items = data };
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
return new QueryData<UpdateZipFile>() { };
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Gateway.Application;
|
||||
@@ -18,7 +20,7 @@ namespace ThingsGateway.Management;
|
||||
[AppStartup(10000000)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureAdminApp(IServiceCollection services)
|
||||
public void Configure(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IRedundancyService, RedundancyService>();
|
||||
services.AddGatewayHostedService<IRedundancyHostedService, RedundancyHostedService>();
|
||||
@@ -29,8 +31,9 @@ public class Startup : AppStartup
|
||||
}
|
||||
|
||||
|
||||
public void UseAdminCore(IServiceProvider serviceProvider)
|
||||
public void Use(IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
var serviceProvider = applicationBuilder.ApplicationServices;
|
||||
//检查ConfigId
|
||||
var configIdGroup = DbContext.DbConfigs.GroupBy(it => it.ConfigId);
|
||||
foreach (var configId in configIdGroup)
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ using TouchSocket.Rpc;
|
||||
|
||||
namespace ThingsGateway.Management;
|
||||
|
||||
public partial class UpgradeRpcServer : RpcServer
|
||||
public partial class UpgradeRpcServer : SingletonRpcServer
|
||||
{
|
||||
[DmtpRpc(MethodInvoke = true)]
|
||||
public void Restart()
|
||||
|
||||
3
src/Gateway/ThingsGateway.RulesEngine/FodyWeavers.xml
Normal file
3
src/Gateway/ThingsGateway.RulesEngine/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Rougamo />
|
||||
</Weavers>
|
||||
67
src/Gateway/ThingsGateway.RulesEngine/Job/LogJob.cs
Normal file
67
src/Gateway/ThingsGateway.RulesEngine/Job/LogJob.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using ThingsGateway.Schedule;
|
||||
|
||||
namespace ThingsGateway.RulesEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 清理日志作业任务
|
||||
/// </summary>
|
||||
[JobDetail("rulesenginejob_log", Description = "清理规则引擎日志", GroupName = "Log", Concurrent = false)]
|
||||
[Daily(TriggerId = "trigger_rulesenginelog", Description = "清理规则引擎日志", RunOnStart = false)]
|
||||
public class LogJob : IJob
|
||||
{
|
||||
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
|
||||
{
|
||||
await DeleteTextLog(stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static async Task DeleteTextLog(CancellationToken stoppingToken)
|
||||
{
|
||||
//网关通道日志以通道id命名
|
||||
var rulesService = App.RootServices.GetService<IRulesService>();
|
||||
var ruleNames = (await rulesService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet();
|
||||
var ruleBaseDir = RulesEngineHostedService.LogDir;
|
||||
Directory.CreateDirectory(ruleBaseDir);
|
||||
|
||||
Delete(ruleBaseDir, ruleNames, stoppingToken);
|
||||
|
||||
}
|
||||
|
||||
private static void Delete(string baseDir, HashSet<string> strings, CancellationToken stoppingToken)
|
||||
{
|
||||
var channelDir = Directory.GetDirectories(baseDir)
|
||||
.Select(a => Path.GetFileName(a))
|
||||
.ToArray();
|
||||
foreach (var dir in channelDir)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//删除文件夹
|
||||
try
|
||||
{
|
||||
if (!strings.Contains(dir))
|
||||
{
|
||||
Directory.Delete(baseDir.CombinePathWithOs(dir), true);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -41,7 +41,10 @@
|
||||
"ThingsGateway.RulesEngine.Rules": {
|
||||
"Name": "Name",
|
||||
"Status": "Status",
|
||||
"SortCode": "SortCode"
|
||||
"SortCode": "SortCode",
|
||||
"ClearRules": "ClearRules",
|
||||
"DeleteRules": "DeleteRules",
|
||||
"SaveRules": "SaveRules"
|
||||
},
|
||||
|
||||
"ThingsGateway.RulesEngine.RulesEngineHostedService": {
|
||||
|
||||
@@ -45,7 +45,10 @@
|
||||
|
||||
"Name": "名称",
|
||||
"Status": "状态",
|
||||
"SortCode": "排序"
|
||||
"SortCode": "排序",
|
||||
"ClearRules": "清空规则",
|
||||
"DeleteRules": "删除规则",
|
||||
"SaveRules": "保存规则"
|
||||
},
|
||||
|
||||
"ThingsGateway.RulesEngine.RulesEngineHostedService": {
|
||||
|
||||
@@ -9,7 +9,64 @@ namespace ThingsGateway.RulesEngine;
|
||||
[CategoryNode(Category = "Actuator", ImgUrl = "_content/ThingsGateway.RulesEngine/img/CSharpScript.svg", Desc = nameof(ExecuteScriptNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(CSharpScriptWidget))]
|
||||
public class ExecuteScriptNode : TextNode, IActuatorNode, IExexcuteExpressionsBase, IDisposable
|
||||
{
|
||||
public ExecuteScriptNode(string id, Point? position = null) : base(id, position) { Title = "ExecuteScriptNode"; Placeholder = "ExecuteScriptNode.Placeholder"; }
|
||||
public ExecuteScriptNode(string id, Point? position = null) : base(id, position)
|
||||
{
|
||||
Title = "ExecuteScriptNode"; Placeholder = "ExecuteScriptNode.Placeholder";
|
||||
Text =
|
||||
"""
|
||||
using ThingsGateway.RulesEngine;
|
||||
using ThingsGateway.Foundation;
|
||||
using TouchSocket.Core;
|
||||
using System.Text;
|
||||
|
||||
public class TestEx : IExexcuteExpressions
|
||||
{
|
||||
|
||||
public TouchSocket.Core.ILog Logger { get; set; }
|
||||
|
||||
public async System.Threading.Tasks.Task<NodeOutput> ExecuteAsync(NodeInput input, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
|
||||
//想上传mqtt,可以自己写mqtt上传代码,或者通过mqtt插件的公开方法上传
|
||||
|
||||
//直接获取mqttclient插件类型的第一个设备
|
||||
var driver = GlobalData.ReadOnlyChannels.FirstOrDefault(a => a.Value.PluginName == "ThingsGateway.Plugin.Mqtt.MqttClient").Value?.ReadDeviceRuntimes?.FirstOrDefault().Value?.Driver;
|
||||
if (driver != null)
|
||||
{
|
||||
//找到对应的MqttClient插件设备
|
||||
var mqttClient = (ThingsGateway.Plugin.Mqtt.MqttClient)driver;
|
||||
if (mqttClient == null)
|
||||
throw new("mqttClient NOT FOUND");
|
||||
var result = await mqttClient.MqttUpAsync("test", Encoding.UTF8.GetBytes("test"),1, default);// 主题 和 负载
|
||||
if (!result.IsSuccess)
|
||||
throw new(result.ErrorMessage);
|
||||
return new NodeOutput() { Value = result };
|
||||
}
|
||||
throw new("mqttClient NOT FOUND");
|
||||
|
||||
|
||||
//通过设备名称找出mqttClient插件
|
||||
//var driver = GlobalData.ReadOnlyDevices.FirstOrDefault(a => a.Value.Name == "mqttDevice1").Value?.Driver;
|
||||
//if (driver != null)
|
||||
//{
|
||||
// //找到对应的MqttClient插件设备
|
||||
// var mqttClient = (ThingsGateway.Plugin.Mqtt.MqttClient)driver;
|
||||
// if (mqttClient == null)
|
||||
// throw new("mqttClient NOT FOUND");
|
||||
// var result = await mqttClient.MqttUpAsync("test", "test", default);// 主题 和 负载
|
||||
// if (!result.IsSuccess)
|
||||
// throw new(result.ErrorMessage);
|
||||
// return new NodeOutput() { Value = result };
|
||||
//}
|
||||
//throw new("mqttClient NOT FOUND");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
""";
|
||||
|
||||
}
|
||||
|
||||
private string text;
|
||||
|
||||
|
||||
@@ -11,7 +11,14 @@ namespace ThingsGateway.RulesEngine;
|
||||
[CategoryNode(Category = "Expression", ImgUrl = "_content/ThingsGateway.RulesEngine/img/CSharpScript.svg", Desc = nameof(ConditionNode), LocalizerType = typeof(ThingsGateway.RulesEngine._Imports), WidgetType = typeof(CSharpScriptWidget))]
|
||||
public class ConditionNode : TextNode, IConditionNode
|
||||
{
|
||||
public ConditionNode(string id, Point? position = null) : base(id, position) { Title = "ConditionNode"; Placeholder = "ConditionNode.Placeholder"; }
|
||||
public ConditionNode(string id, Point? position = null) : base(id, position)
|
||||
{
|
||||
Title = "ConditionNode"; Placeholder = "ConditionNode.Placeholder";
|
||||
Text = "return true;";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Task<bool> IConditionNode.ExecuteAsync(NodeInput input, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user