Compare commits
	
		
			27 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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.189" />
 | 
			
		||||
	</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();
 | 
			
		||||
 
 | 
			
		||||
@@ -298,9 +298,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>>();
 | 
			
		||||
 
 | 
			
		||||
@@ -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.5.12" />
 | 
			
		||||
		<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.8</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.5.8</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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -944,7 +960,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.0" />
 | 
			
		||||
		<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.0" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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)]
 | 
			
		||||
 
 | 
			
		||||
@@ -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"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ namespace ThingsGateway.Gateway.Application;
 | 
			
		||||
public class DeviceRuntime : Device, IDisposable
 | 
			
		||||
{
 | 
			
		||||
    protected volatile DeviceStatusEnum _deviceStatus = DeviceStatusEnum.Default;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    private string? _lastErrorMessage;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -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,7 +900,8 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
 | 
			
		||||
            await NewDeviceLock.WaitAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            await PrivateRemoveDevicesAsync(Drivers.Keys).ConfigureAwait(false);
 | 
			
		||||
            Channel?.SafeDispose();
 | 
			
		||||
            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.0" />
 | 
			
		||||
		<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.0" />
 | 
			
		||||
		<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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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,12 +4,19 @@
 | 
			
		||||
@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">
 | 
			
		||||
                    <Textarea Value="@ProAuthentication.UUID" rows="5" />
 | 
			
		||||
                <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,21 +9,21 @@
 | 
			
		||||
@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)=>{
 | 
			
		||||
        @if (Logger != null)
 | 
			
		||||
        {
 | 
			
		||||
            <LogConsole LogLevel=@(Logger.LogLevel) LogLevelChanged="(a)=>{
 | 
			
		||||
Logger.LogLevel=a;
 | 
			
		||||
}" LogPath=@LogPath HeaderText=@HeaderText></LogConsole>
 | 
			
		||||
    }
 | 
			
		||||
        }
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
                 <Button IsAsync Color="Color.Primary" OnClick="OnUpdate">@ManagementLocalizer["Update"]</Button>
 | 
			
		||||
 | 
			
		||||
            </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)
 | 
			
		||||
 
 | 
			
		||||
@@ -32,11 +32,12 @@
 | 
			
		||||
		<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Services\FilePlugin.cs" Link="Update\Files\FilePlugin.cs" />
 | 
			
		||||
		<Compile Include="..\..\Upgrade\ThingsGateway.Upgrade\Services\UpgradeServerOptions.cs" Link="Update\Files\UpgradeServerOptions.cs" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
	  <ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" />
 | 
			
		||||
	  <ProjectReference Include="..\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj" />
 | 
			
		||||
		<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": {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        Func = func;
 | 
			
		||||
        FuncDict.Add(this, func);
 | 
			
		||||
        if (!DeviceChangedTriggerNodeDict.TryGetValue(Text, out var list))
 | 
			
		||||
        if (!DeviceChangedTriggerNodeDict.TryGetValue(Text ?? string.Empty, out var list))
 | 
			
		||||
        {
 | 
			
		||||
            var deviceChangedTriggerNodes = new ConcurrentList<DeviceChangedTriggerNode>();
 | 
			
		||||
            deviceChangedTriggerNodes.Add(this);
 | 
			
		||||
@@ -44,7 +44,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
 | 
			
		||||
 | 
			
		||||
    private static void GlobalData_DeviceStatusChangeEvent(DeviceRuntime deviceRunTime, DeviceBasicData deviceData)
 | 
			
		||||
    {
 | 
			
		||||
        if (DeviceChangedTriggerNodeDict.TryGetValue(deviceData.Name, out var deviceChangedTriggerNodes) && deviceChangedTriggerNodes?.Count > 0)
 | 
			
		||||
        if (DeviceChangedTriggerNodeDict.TryGetValue(deviceData.Name ?? string.Empty, out var deviceChangedTriggerNodes) && deviceChangedTriggerNodes?.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (!DeviceDatas.IsAddingCompleted)
 | 
			
		||||
            {
 | 
			
		||||
@@ -63,7 +63,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
 | 
			
		||||
        return DeviceDatas.GetConsumingEnumerable().ParallelForEachAsync((async (deviceDatas, token) =>
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                if (DeviceChangedTriggerNodeDict.TryGetValue(deviceDatas.Name, out var valueChangedTriggerNodes))
 | 
			
		||||
                if (DeviceChangedTriggerNodeDict.TryGetValue(deviceDatas.Name ?? string.Empty, out var valueChangedTriggerNodes))
 | 
			
		||||
                {
 | 
			
		||||
                    await valueChangedTriggerNodes.ParallelForEachAsync(async (item, token) =>
 | 
			
		||||
                     {
 | 
			
		||||
@@ -89,7 +89,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        FuncDict.Remove(this);
 | 
			
		||||
        if (DeviceChangedTriggerNodeDict.TryGetValue(Text, out var list))
 | 
			
		||||
        if (DeviceChangedTriggerNodeDict.TryGetValue(Text ?? string.Empty, out var list))
 | 
			
		||||
        {
 | 
			
		||||
            list.Remove(this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,8 @@ public class RulesLog
 | 
			
		||||
 | 
			
		||||
internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngineHostedService
 | 
			
		||||
{
 | 
			
		||||
    internal string LogPathFormat = "Logs/RulesEngineLog/{0}";
 | 
			
		||||
    internal const string LogPathFormat = "Logs/RulesEngineLog/{0}";
 | 
			
		||||
    internal const string LogDir = "Logs/RulesEngineLog";
 | 
			
		||||
    private readonly ILogger _logger;
 | 
			
		||||
    /// <inheritdoc cref="RulesEngineHostedService"/>
 | 
			
		||||
    public RulesEngineHostedService(ILogger<RulesEngineHostedService> logger, IStringLocalizer<RulesEngineHostedService> localizer)
 | 
			
		||||
@@ -99,7 +100,9 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
 | 
			
		||||
 | 
			
		||||
    private (RulesLog rulesLog, BlazorDiagram blazorDiagram) Init(Rules rules)
 | 
			
		||||
    {
 | 
			
		||||
        var log = TextFileLogger.GetMultipleFileLogger(string.Format(LogPathFormat, rules.Id));
 | 
			
		||||
#pragma warning disable CA1863
 | 
			
		||||
        var log = TextFileLogger.GetMultipleFileLogger(string.Format(LogPathFormat, rules.Name));
 | 
			
		||||
#pragma warning restore CA1863
 | 
			
		||||
        log.LogLevel = TouchSocket.Core.LogLevel.Trace;
 | 
			
		||||
        BlazorDiagram blazorDiagram = new();
 | 
			
		||||
        RuleHelpers.Load(blazorDiagram, rules.RulesJson);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using Microsoft.AspNetCore.Builder;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
@@ -20,15 +21,16 @@ namespace ThingsGateway.Management;
 | 
			
		||||
[AppStartup(10000000)]
 | 
			
		||||
public class Startup : AppStartup
 | 
			
		||||
{
 | 
			
		||||
    public void ConfigureAdminApp(IServiceCollection services)
 | 
			
		||||
    public void Configure(IServiceCollection services)
 | 
			
		||||
    {
 | 
			
		||||
        services.AddSingleton<IRulesService, RulesService>();
 | 
			
		||||
        services.AddGatewayHostedService<IRulesEngineHostedService, RulesEngineHostedService>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<ProjectReference Include="..\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj" />
 | 
			
		||||
		<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams\ThingsGateway.Blazor.Diagrams.csproj" />
 | 
			
		||||
		<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
    <h4>
 | 
			
		||||
        <a id="user-content-演示" class="anchor" href="#%E6%BC%94%E7%A4%BA"></a>演示
 | 
			
		||||
    </h4>
 | 
			
		||||
    <p><a href="http://47.119.161.158:5000/">ThingsGateway演示地址</a></p>
 | 
			
		||||
    <p><a href="https://demo.thingsgateway.cn/">ThingsGateway演示地址</a></p>
 | 
			
		||||
    <p>账户	:  <strong>SuperAdmin</strong></p>
 | 
			
		||||
    <p>密码 : <strong>111111</strong></p>
 | 
			
		||||
    <p><strong>右上角个人弹出框中,切换到物联网关模块</strong></p>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using Microsoft.AspNetCore.Builder;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Debug;
 | 
			
		||||
@@ -22,7 +23,7 @@ public class Startup : AppStartup
 | 
			
		||||
   .AddScoped<IMenuService, DefaultMenuService>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void UseService(IServiceProvider serviceProvider)
 | 
			
		||||
    public void Use(IApplicationBuilder applicationBuilder)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -251,7 +251,7 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
            DisplayName = subscriptionName
 | 
			
		||||
        };
 | 
			
		||||
        List<MonitoredItem> monitoredItems = new();
 | 
			
		||||
        var variableNodes = loadType ? await ReadNodesAsync(items, cancellationToken).ConfigureAwait(false) : null;
 | 
			
		||||
        var variableNodes = loadType ? await ReadNodesAsync(items, false, cancellationToken).ConfigureAwait(false) : null;
 | 
			
		||||
        for (int i = 0; i < items.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
@@ -743,7 +743,7 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
                    NodeId = new NodeId(item.Key),
 | 
			
		||||
                    AttributeId = Attributes.Value,
 | 
			
		||||
                };
 | 
			
		||||
                var variableNode = await ReadNodeAsync(item.Key, false, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                var variableNode = await ReadNodeAsync(item.Key, false, false, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                var dataValue = JsonUtils.Decode(
 | 
			
		||||
                    m_session.MessageContext,
 | 
			
		||||
                    variableNode.DataType,
 | 
			
		||||
@@ -793,9 +793,10 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
        {
 | 
			
		||||
            if (m_session != null)
 | 
			
		||||
            {
 | 
			
		||||
                var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false);
 | 
			
		||||
                foreach (var value in monitoreditem.DequeueValues())
 | 
			
		||||
                {
 | 
			
		||||
                    var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode));
 | 
			
		||||
 | 
			
		||||
                    if (value.Value != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
 | 
			
		||||
@@ -974,7 +975,7 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
        List<(string, DataValue, JToken)> jTokens = new();
 | 
			
		||||
        for (int i = 0; i < results.Count; i++)
 | 
			
		||||
        {
 | 
			
		||||
            var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, StatusCode.IsGood(results[i].StatusCode), cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable);
 | 
			
		||||
            var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value);
 | 
			
		||||
            jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken));
 | 
			
		||||
@@ -985,7 +986,7 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 从服务器或缓存读取节点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true)
 | 
			
		||||
    private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true, bool cache = true)
 | 
			
		||||
    {
 | 
			
		||||
        if (!isOnlyServer)
 | 
			
		||||
        {
 | 
			
		||||
@@ -1025,14 +1026,15 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
 | 
			
		||||
        VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
        _variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
 | 
			
		||||
        if (cache)
 | 
			
		||||
            _variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
 | 
			
		||||
        return variableNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 从服务器或缓存读取节点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, CancellationToken cancellationToken = default)
 | 
			
		||||
    private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, bool cache = true, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        if (!isOnlyServer)
 | 
			
		||||
        {
 | 
			
		||||
@@ -1073,7 +1075,9 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
 | 
			
		||||
        if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject)
 | 
			
		||||
            await typeSystem.LoadType(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false);
 | 
			
		||||
        _variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
 | 
			
		||||
 | 
			
		||||
        if (cache)
 | 
			
		||||
            _variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
 | 
			
		||||
        return variableNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1127,7 +1131,7 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 从服务器读取节点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, CancellationToken cancellationToken = default)
 | 
			
		||||
    private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, bool cache = false, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        List<Node> result = new(nodeIdStrs.Length);
 | 
			
		||||
        foreach (var items in nodeIdStrs.ChunkBetter(OpcUaProperty.GroupSize))
 | 
			
		||||
@@ -1171,7 +1175,8 @@ public class OpcUaMaster : IDisposable
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    _variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node);
 | 
			
		||||
                    if (cache)
 | 
			
		||||
                        _variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node);
 | 
			
		||||
                    if (node.DataType != NodeId.Null && TypeInfo.GetBuiltInType(node.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject)
 | 
			
		||||
                    {
 | 
			
		||||
                        await typeSystem.LoadType(node.DataType, ct: cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
 | 
			
		||||
		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.375.457" />
 | 
			
		||||
		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.213" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -184,7 +184,7 @@ public static class JsonUtils
 | 
			
		||||
    /// CreateEncoder
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    private static OPCUAJsonEncoder CreateEncoder(
 | 
			
		||||
    private static JsonEncoder CreateEncoder(
 | 
			
		||||
        IServiceMessageContext context,
 | 
			
		||||
        Stream stream,
 | 
			
		||||
        bool useReversibleEncoding = false,
 | 
			
		||||
@@ -193,14 +193,14 @@ public static class JsonUtils
 | 
			
		||||
        bool includeDefaultNumbers = true
 | 
			
		||||
        )
 | 
			
		||||
    {
 | 
			
		||||
        return new OPCUAJsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream)
 | 
			
		||||
        return new JsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream)
 | 
			
		||||
        {
 | 
			
		||||
            IncludeDefaultValues = includeDefaultValues,
 | 
			
		||||
            IncludeDefaultNumberValues = includeDefaultNumbers
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void Encode(OPCUAJsonEncoder encoder, BuiltInType builtInType, string fieldName, object value)
 | 
			
		||||
    private static void Encode(JsonEncoder encoder, BuiltInType builtInType, string fieldName, object value)
 | 
			
		||||
    {
 | 
			
		||||
        bool isArray = (value?.GetType().IsArray ?? false) && (builtInType != BuiltInType.ByteString);
 | 
			
		||||
        bool isCollection = (value is IList) && (builtInType != BuiltInType.ByteString);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,28 @@
 | 
			
		||||
<Project>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		<Target Name="CopyNugetPackages" AfterTargets="Build">
 | 
			
		||||
			<ItemGroup>
 | 
			
		||||
				<!-- setting up the variable for convenience -->
 | 
			
		||||
				<!--<SqlSugar_QuestDb_RestAPIApplicationPackageFiles Include="$(PkgSqlSugar_QuestDb_RestAPI)\lib\netstandard2.1\*.*" />
 | 
			
		||||
				<CsvHelperApplicationPackageFiles Include="$(PkgCsvHelper)\lib\net8.0\*.*" />-->
 | 
			
		||||
				<SqlSugar_TDengineCoreApplicationPackageFiles Include="$(PkgSqlSugar_TDengineCore)\lib\netstandard2.1\*.*" />
 | 
			
		||||
				<TDengine_Ado_DataApplicationPackageFiles Include="$(PkgTDengine_Ado_Data)\lib\netstandard2.1\*.*" />
 | 
			
		||||
				<TDengine_ConnectorPackageFiles Include="$(PkgTDengine_Connector)\lib\net6.0\*.*" />
 | 
			
		||||
			</ItemGroup>
 | 
			
		||||
			<PropertyGroup>
 | 
			
		||||
				<ApplicationFolder>$(TargetDir)</ApplicationFolder>
 | 
			
		||||
			</PropertyGroup>
 | 
			
		||||
			<Copy SourceFiles="@(SqlSugar_QuestDb_RestAPIApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
			<Copy SourceFiles="@(CsvHelperApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
			<Copy SourceFiles="@(SqlSugar_TDengineCoreApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
			<Copy SourceFiles="@(TDengine_Ado_DataApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
			<Copy SourceFiles="@(TDengine_ConnectorPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
		</Target>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<!--在构建后触发的。它通过在 Nuget 包的 Content 文件夹中包含目标目录中的所有文件和子文件夹来创建 nuget 包-->
 | 
			
		||||
	<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build">
 | 
			
		||||
 | 
			
		||||
@@ -9,6 +31,18 @@
 | 
			
		||||
				<Pack>true</Pack>
 | 
			
		||||
				<PackagePath>Content</PackagePath>
 | 
			
		||||
			</Content>
 | 
			
		||||
			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*TDengine*.dll">
 | 
			
		||||
				<Pack>true</Pack>
 | 
			
		||||
				<PackagePath>Content</PackagePath>
 | 
			
		||||
			</Content>
 | 
			
		||||
			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*QuestDb*.dll">
 | 
			
		||||
				<Pack>true</Pack>
 | 
			
		||||
				<PackagePath>Content</PackagePath>
 | 
			
		||||
			</Content>
 | 
			
		||||
			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*CsvHelper*.dll">
 | 
			
		||||
				<Pack>true</Pack>
 | 
			
		||||
				<PackagePath>Content</PackagePath>
 | 
			
		||||
			</Content>
 | 
			
		||||
		</ItemGroup>
 | 
			
		||||
	</Target>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@
 | 
			
		||||
    "BigTextScriptHistoryTable": "DynamicScriptHistoryTable",
 | 
			
		||||
    "BigTextScriptRealTable": "DynamicScriptRealTable",
 | 
			
		||||
    "RealTableBusinessInterval": "RealTableBusinessInterval",
 | 
			
		||||
    "SqlDBSplitType": "SplitType",
 | 
			
		||||
 | 
			
		||||
    "BusinessUpdateEnum": "BusinessUpdateEnum",
 | 
			
		||||
    "BusinessInterval": "BusinessInterval",
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@
 | 
			
		||||
    "BigTextScriptHistoryTable": "历史表动态脚本",
 | 
			
		||||
    "BigTextScriptRealTable": "实时表动态脚本",
 | 
			
		||||
    "RealTableBusinessInterval": "实时表定时上传间隔",
 | 
			
		||||
 | 
			
		||||
    "SqlDBSplitType": "分表模式",
 | 
			
		||||
    "BusinessUpdateEnum": "上传模式",
 | 
			
		||||
    "BusinessInterval": "定时上传间隔",
 | 
			
		||||
    "IsAllVariable": "选择全部变量",
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user