mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-11-04 09:33:58 +08:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			10.11.70.0
			...
			10.11.85.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					53a98b26cd | ||
| 
						 | 
					42c740fa1b | ||
| 
						 | 
					556819c90c | ||
| 
						 | 
					2522333a9c | ||
| 
						 | 
					bd4ce7c09b | ||
| 
						 | 
					156ed88bd6 | ||
| 
						 | 
					2416226eb0 | ||
| 
						 | 
					976323a716 | ||
| 
						 | 
					3c9e397403 | ||
| 
						 | 
					79406ad4a0 | ||
| 
						 | 
					20c44f10ca | ||
| 
						 | 
					31d6b2a9e6 | ||
| 
						 | 
					68e5a9c546 | 
@@ -45,6 +45,7 @@ public class VerificatInfo : PrimaryIdEntity
 | 
			
		||||
    /// 登录IP
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)]
 | 
			
		||||
    [SugarColumn(IsNullable = true)]
 | 
			
		||||
    public string LoginIp { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -78,5 +79,6 @@ public class VerificatInfo : PrimaryIdEntity
 | 
			
		||||
    /// 登录设备
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)]
 | 
			
		||||
    [SugarColumn(IsNullable = true)]
 | 
			
		||||
    public string Device { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -145,7 +145,7 @@ public class AdminOAuthHandler<TOptions>(
 | 
			
		||||
        var loginEvent = new LoginEvent
 | 
			
		||||
        {
 | 
			
		||||
            Ip = appService.RemoteIpAddress,
 | 
			
		||||
            Device = appService.UserAgent?.Platform,
 | 
			
		||||
            Device = appService.UserAgent?.Platform ?? "Unknown",
 | 
			
		||||
            Expire = expire,
 | 
			
		||||
            SysUser = sysUser,
 | 
			
		||||
            VerificatId = CommonUtils.GetSingleId()
 | 
			
		||||
@@ -156,7 +156,7 @@ public class AdminOAuthHandler<TOptions>(
 | 
			
		||||
        //生成verificat信息
 | 
			
		||||
        var verificatInfo = new VerificatInfo
 | 
			
		||||
        {
 | 
			
		||||
            Device = loginEvent.Device,
 | 
			
		||||
            Device = loginEvent.Device ?? "Unknown",
 | 
			
		||||
            Expire = loginEvent.Expire,
 | 
			
		||||
            VerificatTimeout = tokenTimeout,
 | 
			
		||||
            Id = loginEvent.VerificatId,
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@
 | 
			
		||||
      "Module": 2,
 | 
			
		||||
      "Title": "权限管理",
 | 
			
		||||
      "Code": "System",
 | 
			
		||||
      "NavLinkMatch": "All",
 | 
			
		||||
      "NavLinkMatch": "Prefix",
 | 
			
		||||
      "Category": "MENU",
 | 
			
		||||
      "Target": "_self",
 | 
			
		||||
      "Href": null,
 | 
			
		||||
@@ -47,7 +47,7 @@
 | 
			
		||||
      "ParentId": 0,
 | 
			
		||||
      "Module": 2,
 | 
			
		||||
      "Title": "系统运维",
 | 
			
		||||
      "NavLinkMatch": "All",
 | 
			
		||||
      "NavLinkMatch": "Prefix",
 | 
			
		||||
      "Code": "System",
 | 
			
		||||
      "Category": "MENU",
 | 
			
		||||
      "Target": "_self",
 | 
			
		||||
 
 | 
			
		||||
@@ -235,7 +235,7 @@ public class AuthService : IAuthService
 | 
			
		||||
        var logingEvent = new LoginEvent
 | 
			
		||||
        {
 | 
			
		||||
            Ip = _appService.RemoteIpAddress,
 | 
			
		||||
            Device = _appService.UserAgent?.Platform,
 | 
			
		||||
            Device = _appService.UserAgent?.Platform ?? "Unknown",
 | 
			
		||||
            Expire = expire,
 | 
			
		||||
            SysUser = sysUser,
 | 
			
		||||
            VerificatId = verificatId
 | 
			
		||||
@@ -344,7 +344,7 @@ public class AuthService : IAuthService
 | 
			
		||||
        //生成verificat信息
 | 
			
		||||
        var verificatInfo = new VerificatInfo
 | 
			
		||||
        {
 | 
			
		||||
            Device = loginEvent.Device,
 | 
			
		||||
            Device = loginEvent.Device ?? "Unknown",
 | 
			
		||||
            Expire = loginEvent.Expire,
 | 
			
		||||
            VerificatTimeout = tokenTimeout,
 | 
			
		||||
            Id = loginEvent.VerificatId,
 | 
			
		||||
 
 | 
			
		||||
@@ -125,13 +125,22 @@ public class BlazorAppContext
 | 
			
		||||
            var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId);
 | 
			
		||||
            OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList();
 | 
			
		||||
            AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList();
 | 
			
		||||
            OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem()
 | 
			
		||||
            OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item =>
 | 
			
		||||
            {
 | 
			
		||||
                Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,
 | 
			
		||||
                Text = item.Title,
 | 
			
		||||
                Icon = item.Icon,
 | 
			
		||||
                Url = item.Href,
 | 
			
		||||
                Target = item.Target.ToString(),
 | 
			
		||||
                var menu = new MenuItem()
 | 
			
		||||
                {
 | 
			
		||||
                    Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix,
 | 
			
		||||
                    Text = item.Title,
 | 
			
		||||
                    Icon = item.Icon,
 | 
			
		||||
                    Url = item.Href,
 | 
			
		||||
                    Target = item.Target.ToString(),
 | 
			
		||||
                };
 | 
			
		||||
                if (menu.Url.IsNullOrEmpty())
 | 
			
		||||
                {
 | 
			
		||||
                    menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix;
 | 
			
		||||
                }
 | 
			
		||||
                return menu;
 | 
			
		||||
 | 
			
		||||
            }).ToList();
 | 
			
		||||
            UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -41,15 +41,22 @@ public static class AdminResourceUtil
 | 
			
		||||
        return items
 | 
			
		||||
        .Where(it => it.ParentId == parentId)
 | 
			
		||||
        .Select((item, index) =>
 | 
			
		||||
            new MenuItem()
 | 
			
		||||
        {
 | 
			
		||||
            var menu = new MenuItem()
 | 
			
		||||
            {
 | 
			
		||||
                Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,
 | 
			
		||||
                Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix,
 | 
			
		||||
                Text = item.Title,
 | 
			
		||||
                Icon = item.Icon,
 | 
			
		||||
                Url = item.Href,
 | 
			
		||||
                Target = item.Target.ToString(),
 | 
			
		||||
                Items = BuildMenuTrees(items, item.Id).ToList()
 | 
			
		||||
            };
 | 
			
		||||
            if (menu.Url.IsNullOrEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix;
 | 
			
		||||
            }
 | 
			
		||||
            return menu;
 | 
			
		||||
        }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,12 +21,12 @@
 | 
			
		||||
    <link rel="apple-touch-icon" href="favicon.png">
 | 
			
		||||
    <base href="/" />
 | 
			
		||||
    <title>ThingsGateway</title>
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css") />
 | 
			
		||||
 | 
			
		||||
    @* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@
 | 
			
		||||
    <!-- PWA Manifest -->
 | 
			
		||||
@@ -40,8 +40,8 @@
 | 
			
		||||
 | 
			
		||||
    <BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
 | 
			
		||||
 | 
			
		||||
    <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script>
 | 
			
		||||
    <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script>
 | 
			
		||||
    <script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js")></script>
 | 
			
		||||
    <script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
 | 
			
		||||
    <script src="_framework/blazor.web.js"></script>
 | 
			
		||||
    <!-- PWA Service Worker -->
 | 
			
		||||
    <script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@
 | 
			
		||||
                        <Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
 | 
			
		||||
                    }
 | 
			
		||||
                    @* 版本号 *@
 | 
			
		||||
                    <div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
 | 
			
		||||
                    <div class="px-1 navbar-header-text text-nowrap d-none d-lg-block">@_versionString</div>
 | 
			
		||||
 | 
			
		||||
                    @* 主题切换 *@
 | 
			
		||||
                    @* <ThemeToggle /> *@
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,9 @@
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" />
 | 
			
		||||
		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor" Version="9.10.2" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor" Version="9.11.0" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@
 | 
			
		||||
// ------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.DataEncryption;
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +35,7 @@ public static class PBKDF2Encryption
 | 
			
		||||
        var salt = new byte[saltSize];
 | 
			
		||||
        rng.GetBytes(salt);
 | 
			
		||||
#if NET10_0_OR_GREATER
 | 
			
		||||
         var hash = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(text), salt, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
 | 
			
		||||
        var hash = Rfc2898DeriveBytes.Pbkdf2(System.Text.Encoding.UTF8.GetBytes(text), salt, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
 | 
			
		||||
#else
 | 
			
		||||
        using var pbkdf2 = new Rfc2898DeriveBytes(text, salt, iterationCount, HashAlgorithmName.SHA256);
 | 
			
		||||
        var hash = pbkdf2.GetBytes(derivedKeyLength);
 | 
			
		||||
@@ -70,10 +69,10 @@ public static class PBKDF2Encryption
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
#if NET10_0_OR_GREATER
 | 
			
		||||
            var computedHash = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(text), saltBytes, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
 | 
			
		||||
            var computedHash = Rfc2898DeriveBytes.Pbkdf2(System.Text.Encoding.UTF8.GetBytes(text), saltBytes, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
 | 
			
		||||
#else
 | 
			
		||||
        using var pbkdf2 = new Rfc2898DeriveBytes(text, saltBytes, iterationCount, HashAlgorithmName.SHA256);
 | 
			
		||||
        var computedHash = pbkdf2.GetBytes(derivedKeyLength);
 | 
			
		||||
            using var pbkdf2 = new Rfc2898DeriveBytes(text, saltBytes, iterationCount, HashAlgorithmName.SHA256);
 | 
			
		||||
            var computedHash = pbkdf2.GetBytes(derivedKeyLength);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
            return computedHash.SequenceEqual(storedHashBytes);
 | 
			
		||||
 
 | 
			
		||||
@@ -204,7 +204,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogWarning("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count);
 | 
			
		||||
                _logger.LogInformation("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
 | 
			
		||||
		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
 | 
			
		||||
		<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET10Version)" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										169
									
								
								src/Admin/ThingsGateway.NewLife.X/Common/FastMapper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								src/Admin/ThingsGateway.NewLife.X/Common/FastMapper.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,169 @@
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.NewLife;
 | 
			
		||||
 | 
			
		||||
public class FastMapperOption
 | 
			
		||||
{
 | 
			
		||||
    public Dictionary<string, string> MapperProperties { get; set; } = new();
 | 
			
		||||
    public HashSet<string> IgnoreProperties { get; set; } = new();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public static class FastMapper
 | 
			
		||||
{
 | 
			
		||||
    // 泛型 + 非泛型共用缓存
 | 
			
		||||
    private static readonly ConcurrentDictionary<(Type Source, Type Target), Delegate> _mapCache
 | 
			
		||||
        = new ConcurrentDictionary<(Type, Type), Delegate>();
 | 
			
		||||
 | 
			
		||||
    #region 泛型入口
 | 
			
		||||
    public static TTarget Mapper<TSource, TTarget>(TSource source, FastMapperOption option = null)
 | 
			
		||||
        where TTarget : class, new()
 | 
			
		||||
    {
 | 
			
		||||
        if (source == null) return null;
 | 
			
		||||
 | 
			
		||||
        var key = (typeof(TSource), typeof(TTarget));
 | 
			
		||||
        if (!_mapCache.TryGetValue(key, out var del))
 | 
			
		||||
        {
 | 
			
		||||
            del = CreateMapFunc<TSource, TTarget>();
 | 
			
		||||
            _mapCache[key] = del;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var func = (Func<TSource, FastMapperOption, TTarget>)del;
 | 
			
		||||
        return func(source, option);
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 非泛型入口
 | 
			
		||||
    public static object Mapper(object source, Type targetType, FastMapperOption option = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (source == null) return null;
 | 
			
		||||
 | 
			
		||||
        var sourceType = source.GetType();
 | 
			
		||||
        var key = (sourceType, targetType);
 | 
			
		||||
 | 
			
		||||
        if (!_mapCache.TryGetValue(key, out var del))
 | 
			
		||||
        {
 | 
			
		||||
            // 动态生成泛型委托并缓存
 | 
			
		||||
            var method = typeof(FastMapper).GetMethod(nameof(CreateMapFunc), BindingFlags.NonPublic | BindingFlags.Static);
 | 
			
		||||
            var genericMethod = method.MakeGenericMethod(sourceType, targetType);
 | 
			
		||||
            del = genericMethod.Invoke(null, null) as Delegate;
 | 
			
		||||
            _mapCache[key] = del;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return del.DynamicInvoke(source, option);
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region Expression Tree 创建委托
 | 
			
		||||
    private static Func<TSource, FastMapperOption, TTarget> CreateMapFunc<TSource, TTarget>()
 | 
			
		||||
        where TTarget : class, new()
 | 
			
		||||
    {
 | 
			
		||||
        var sourceType = typeof(TSource);
 | 
			
		||||
        var targetType = typeof(TTarget);
 | 
			
		||||
 | 
			
		||||
        var sourceParam = Expression.Parameter(sourceType, "src");
 | 
			
		||||
        var optionParam = Expression.Parameter(typeof(FastMapperOption), "opt");
 | 
			
		||||
        var targetVar = Expression.Variable(targetType, "dest");
 | 
			
		||||
 | 
			
		||||
        var expressions = new List<Expression>
 | 
			
		||||
        {
 | 
			
		||||
            Expression.Assign(targetVar, Expression.New(targetType))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
 | 
			
		||||
        var targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
 | 
			
		||||
        var targetDict = targetProperties.Where(p => p.CanWrite).ToDictionary(p => p.Name);
 | 
			
		||||
 | 
			
		||||
        foreach (var sp in sourceProperties)
 | 
			
		||||
        {
 | 
			
		||||
            if (!sp.CanRead) continue;
 | 
			
		||||
 | 
			
		||||
            // FastMapperOption 重命名
 | 
			
		||||
            var pNameExpr = Expression.Constant(sp.Name);
 | 
			
		||||
            if (targetDict.TryGetValue(sp.Name, out PropertyInfo? tp))
 | 
			
		||||
            {
 | 
			
		||||
                // Ignore check
 | 
			
		||||
                Expression ignoreCheck = Expression.Call(
 | 
			
		||||
                    Expression.Property(optionParam, nameof(FastMapperOption.IgnoreProperties)),
 | 
			
		||||
                    nameof(HashSet<string>.Contains),
 | 
			
		||||
                    null,
 | 
			
		||||
                    pNameExpr
 | 
			
		||||
                );
 | 
			
		||||
                Expression assign;
 | 
			
		||||
 | 
			
		||||
                // 1️⃣ 简单类型直接赋值
 | 
			
		||||
                if (IsSimpleType(sp.PropertyType))
 | 
			
		||||
                {
 | 
			
		||||
                    assign = Expression.Assign(
 | 
			
		||||
                        Expression.Property(targetVar, tp),
 | 
			
		||||
                        Expression.Convert(Expression.Property(sourceParam, sp), tp.PropertyType)
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                // 2️⃣ 集合类型
 | 
			
		||||
                else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(sp.PropertyType) && sp.PropertyType != typeof(string))
 | 
			
		||||
                {
 | 
			
		||||
                    var elementSourceType = sp.PropertyType.IsArray
 | 
			
		||||
                        ? sp.PropertyType.GetElementType()
 | 
			
		||||
                        : sp.PropertyType.GetGenericArguments().FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
                    var elementTargetType = tp.PropertyType.IsArray
 | 
			
		||||
                        ? tp.PropertyType.GetElementType()
 | 
			
		||||
                        : tp.PropertyType.GetGenericArguments().FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
                    if (elementSourceType != null && elementTargetType != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        var mapListMethod = typeof(FastMapper).GetMethod(nameof(MapList), BindingFlags.Public | BindingFlags.Static)
 | 
			
		||||
                            .MakeGenericMethod(elementSourceType, elementTargetType);
 | 
			
		||||
 | 
			
		||||
                        assign = Expression.Assign(
 | 
			
		||||
                            Expression.Property(targetVar, tp),
 | 
			
		||||
                            Expression.Call(mapListMethod, Expression.Property(sourceParam, sp), optionParam)
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                    else continue;
 | 
			
		||||
                }
 | 
			
		||||
                // 3️⃣ 引用类型/嵌套对象
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    var mapMethod = typeof(FastMapper).GetMethod(nameof(Mapper), new Type[] { typeof(object), typeof(Type), typeof(FastMapperOption) });
 | 
			
		||||
                    var arg0 = Expression.Convert(Expression.Property(sourceParam, sp), typeof(object)); // ✅ fix Nullable/ValueType
 | 
			
		||||
                    assign = Expression.Assign(
 | 
			
		||||
                        Expression.Property(targetVar, tp),
 | 
			
		||||
                        Expression.Convert(
 | 
			
		||||
                            Expression.Call(mapMethod, arg0, Expression.Constant(tp.PropertyType), optionParam),
 | 
			
		||||
                            tp.PropertyType
 | 
			
		||||
                        )
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                expressions.Add(assign);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expressions.Add(targetVar);
 | 
			
		||||
        var body = Expression.Block(new[] { targetVar }, expressions);
 | 
			
		||||
        return Expression.Lambda<Func<TSource, FastMapperOption, TTarget>>(body, sourceParam, optionParam).Compile();
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    #region 泛型集合映射
 | 
			
		||||
    public static IEnumerable<TTarget> MapList<TSource, TTarget>(IEnumerable<TSource> list, FastMapperOption option = null)
 | 
			
		||||
        where TTarget : class, new()
 | 
			
		||||
    {
 | 
			
		||||
        if (list == null) yield break;
 | 
			
		||||
        foreach (var item in list)
 | 
			
		||||
            yield return Mapper<TSource, TTarget>(item, option);
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    private static bool IsSimpleType(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        return type.IsPrimitive
 | 
			
		||||
               || type.IsEnum
 | 
			
		||||
               || type == typeof(string)
 | 
			
		||||
               || type == typeof(decimal)
 | 
			
		||||
               || type == typeof(DateTime)
 | 
			
		||||
               || (Nullable.GetUnderlyingType(type) != null && IsSimpleType(Nullable.GetUnderlyingType(type)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.1" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.2" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -28,13 +28,13 @@
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
 | 
			
		||||
		<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET10Version)" />-->
 | 
			
		||||
		<PackageReference Include="MySqlConnector" Version="2.4.0" />
 | 
			
		||||
		<PackageReference Include="Npgsql" Version="9.0.3" />
 | 
			
		||||
		<PackageReference Include="Npgsql" Version="9.0.4" />
 | 
			
		||||
		<PackageReference Include="CsvHelper" Version="33.1.0" />
 | 
			
		||||
		<PackageReference Include="TDengine.Connector" Version="3.1.9" />
 | 
			
		||||
		<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
 | 
			
		||||
		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.26" />
 | 
			
		||||
		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.27" />
 | 
			
		||||
		<PackageReference Include="System.Data.Common" Version="4.3.0" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" />
 | 
			
		||||
		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
 | 
			
		||||
		<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
 | 
			
		||||
		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<Project>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<PluginVersion>10.11.70</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.11.70</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.11.70</DefaultVersion>
 | 
			
		||||
		<PluginVersion>10.11.85</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.11.85</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.11.85</DefaultVersion>
 | 
			
		||||
		<AuthenticationVersion>10.11.6</AuthenticationVersion>
 | 
			
		||||
		<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
 | 
			
		||||
		<NET8Version>8.0.20</NET8Version>
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
		<IsTrimmable>false</IsTrimmable>
 | 
			
		||||
		<ManagementProPluginVersion>10.11.70</ManagementProPluginVersion>
 | 
			
		||||
		<ManagementPluginVersion>10.11.70</ManagementPluginVersion>
 | 
			
		||||
		<TSVersion>4.0.0-beta.57</TSVersion>
 | 
			
		||||
		<TSVersion>4.0.0-beta.96</TSVersion>
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
@@ -73,7 +73,7 @@
 | 
			
		||||
		<EmbedAllSources>True</EmbedAllSources>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="Roslynator.Analyzers" Version="4.14.0">
 | 
			
		||||
		<PackageReference Include="Roslynator.Analyzers" Version="4.14.1">
 | 
			
		||||
			<PrivateAssets>all</PrivateAssets>
 | 
			
		||||
			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
			
		||||
		</PackageReference>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="CS-Script" Version="4.11.0" />
 | 
			
		||||
		<PackageReference Include="CS-Script" Version="4.11.2" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@
 | 
			
		||||
                                                            <EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
                                                            <EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
                                                            <EditorItem @bind-Field="@context.StreamAsync" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
                                                            <EditorItem @bind-Field="@context.Handshake" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                                                            <EditorItem @bind-Field="@context.CacheTimeout" Ignore=@(context.ChannelType == ChannelTypeEnum.UdpSession || context.ChannelType == ChannelTypeEnum.Other) />
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@
 | 
			
		||||
    "RemoteUrl": "Remote IP Address",
 | 
			
		||||
    "RtsEnable": "Rts",
 | 
			
		||||
    "StreamAsync": "StreamAsync",
 | 
			
		||||
    "Handshake": "Handshake",
 | 
			
		||||
    "SaveChannel": "Add/Modify Channel",
 | 
			
		||||
    "StopBits": "Stop Bits"
 | 
			
		||||
  },
 | 
			
		||||
@@ -103,6 +104,7 @@
 | 
			
		||||
    "RemoteUrl": "RemoteUrl",
 | 
			
		||||
    "RtsEnable": "RtsEnable",
 | 
			
		||||
    "StreamAsync": "StreamAsync",
 | 
			
		||||
    "Handshake": "Handshake",
 | 
			
		||||
    "StopBits": "StopBits"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -40,7 +40,8 @@
 | 
			
		||||
    "PortName": "COM口",
 | 
			
		||||
    "RemoteUrl": "远程url",
 | 
			
		||||
    "RtsEnable": "Rts",
 | 
			
		||||
    "StreamAsync": "串口流读写",
 | 
			
		||||
    "StreamAsync": "流读写",
 | 
			
		||||
    "Handshake": "Handshake",
 | 
			
		||||
    "SaveChannel": "添加/修改通道",
 | 
			
		||||
    "StopBits": "停止位"
 | 
			
		||||
  },
 | 
			
		||||
@@ -100,7 +101,8 @@
 | 
			
		||||
    "PortName": "COM口",
 | 
			
		||||
    "RemoteUrl": "远程url",
 | 
			
		||||
    "RtsEnable": "Rts",
 | 
			
		||||
    "StreamAsync": "串口流读写",
 | 
			
		||||
    "StreamAsync": "流读写",
 | 
			
		||||
    "Handshake": "串口流读写",
 | 
			
		||||
    "StopBits": "停止位"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -69,15 +69,17 @@ namespace ThingsGateway.Foundation
 | 
			
		||||
        public virtual bool DtrEnable { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// StreamAsync
 | 
			
		||||
        /// Handshake
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public virtual bool StreamAsync { get; set; } = false;
 | 
			
		||||
        public virtual Handshake Handshake { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// RtsEnable
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public virtual bool RtsEnable { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
        public virtual bool StreamAsync { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        [MinValue(1)]
 | 
			
		||||
        public virtual int MaxConcurrentCount { get; set; } = 1;
 | 
			
		||||
 
 | 
			
		||||
@@ -99,16 +99,36 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
        if (channelOptions.MaxClientCount > 0)
 | 
			
		||||
            config.SetMaxCount(channelOptions.MaxClientCount);
 | 
			
		||||
 | 
			
		||||
        config.SetTransportOption(new TouchSocket.Sockets.TransportOption()
 | 
			
		||||
        //    config.SetTransportOption(new TransportOption()
 | 
			
		||||
        //    {
 | 
			
		||||
        //        MaxBufferSize = 1024,
 | 
			
		||||
        //        MinBufferSize = 512,
 | 
			
		||||
        //        SendPipeOptions = new System.IO.Pipelines.PipeOptions(
 | 
			
		||||
        // minimumSegmentSize: 512,
 | 
			
		||||
        // pauseWriterThreshold: 1024,
 | 
			
		||||
        // resumeWriterThreshold: 512,
 | 
			
		||||
        // useSynchronizationContext: false),
 | 
			
		||||
        //        ReceivePipeOptions = new System.IO.Pipelines.PipeOptions(
 | 
			
		||||
        //minimumSegmentSize: 512,
 | 
			
		||||
        // pauseWriterThreshold: 1024,
 | 
			
		||||
        // resumeWriterThreshold: 512,
 | 
			
		||||
        //    useSynchronizationContext: false),
 | 
			
		||||
        //    });
 | 
			
		||||
 | 
			
		||||
        config.SetTransportOption(a =>
 | 
			
		||||
        {
 | 
			
		||||
            SendPipeOptions = new System.IO.Pipelines.PipeOptions(
 | 
			
		||||
             minimumSegmentSize: 1024,
 | 
			
		||||
             useSynchronizationContext: false),
 | 
			
		||||
            ReceivePipeOptions = new System.IO.Pipelines.PipeOptions(
 | 
			
		||||
             minimumSegmentSize: 1024,
 | 
			
		||||
                pauseWriterThreshold: 1024 * 1024,
 | 
			
		||||
                resumeWriterThreshold: 1024 * 512,
 | 
			
		||||
                useSynchronizationContext: false)
 | 
			
		||||
            a.MaxBufferSize = 1024;
 | 
			
		||||
            a.MinBufferSize = 512;
 | 
			
		||||
            a.SendPipeOptions = new System.IO.Pipelines.PipeOptions(
 | 
			
		||||
             minimumSegmentSize: 512,
 | 
			
		||||
             pauseWriterThreshold: 1024,
 | 
			
		||||
             resumeWriterThreshold: 512,
 | 
			
		||||
             useSynchronizationContext: false);
 | 
			
		||||
            a.ReceivePipeOptions = new System.IO.Pipelines.PipeOptions(
 | 
			
		||||
            minimumSegmentSize: 512,
 | 
			
		||||
             pauseWriterThreshold: 1024,
 | 
			
		||||
             resumeWriterThreshold: 512,
 | 
			
		||||
                useSynchronizationContext: false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        switch (channelType)
 | 
			
		||||
@@ -140,10 +160,20 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    private static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    {
 | 
			
		||||
        var serialPortOption = channelOptions.Map<SerialPortOption>();
 | 
			
		||||
        serialPortOption.ThrowIfNull(nameof(SerialPortOption));
 | 
			
		||||
        channelOptions.ThrowIfNull(nameof(SerialPortOption));
 | 
			
		||||
        channelOptions.Config = config;
 | 
			
		||||
        config.SetSerialPortOption(serialPortOption);
 | 
			
		||||
        config.SetSerialPortOption(options =>
 | 
			
		||||
        {
 | 
			
		||||
            options.PortName = channelOptions.PortName;
 | 
			
		||||
            options.BaudRate = channelOptions.BaudRate;
 | 
			
		||||
            options.DataBits = channelOptions.DataBits;
 | 
			
		||||
            options.Parity = channelOptions.Parity;
 | 
			
		||||
            options.StopBits = channelOptions.StopBits;
 | 
			
		||||
            options.DtrEnable = channelOptions.DtrEnable;
 | 
			
		||||
            options.RtsEnable = channelOptions.RtsEnable;
 | 
			
		||||
            options.Handshake = channelOptions.Handshake;
 | 
			
		||||
            options.StreamAsync = channelOptions.StreamAsync;
 | 
			
		||||
        });
 | 
			
		||||
        //载入配置
 | 
			
		||||
        SerialPortChannel serialPortChannel = new SerialPortChannel(channelOptions);
 | 
			
		||||
        return serialPortChannel;
 | 
			
		||||
 
 | 
			
		||||
@@ -72,11 +72,9 @@ public interface IChannelOptions
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    bool RtsEnable { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// StreamAsync
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    bool StreamAsync { get; set; }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    Handshake Handshake { get; set; }
 | 
			
		||||
    #endregion
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 最大并发数量
 | 
			
		||||
 
 | 
			
		||||
@@ -179,7 +179,12 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingA
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
 | 
			
		||||
        }
 | 
			
		||||
        var span = writer.GetSpan(sendMessage.MaxLength);
 | 
			
		||||
        Span<byte> span = default;
 | 
			
		||||
        if (Logger?.LogLevel <= LogLevel.Trace)
 | 
			
		||||
        {
 | 
			
		||||
            span = writer.GetSpan(sendMessage.MaxLength);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sendMessage.Build(ref writer);
 | 
			
		||||
        if (Logger?.LogLevel <= LogLevel.Trace)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,6 @@ using ThingsGateway.NewLife;
 | 
			
		||||
using ThingsGateway.NewLife.Collections;
 | 
			
		||||
using ThingsGateway.NewLife.Extension;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.SerialPorts;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
@@ -350,7 +348,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task BeforeSendAsync(IClientChannel channel, CancellationToken token)
 | 
			
		||||
    private ValueTask BeforeSendAsync(IClientChannel channel, CancellationToken token)
 | 
			
		||||
    {
 | 
			
		||||
        SetDataAdapter(channel);
 | 
			
		||||
        if (AutoConnect && Channel != null && Channel?.Online != true)
 | 
			
		||||
@@ -359,13 +357,13 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
            return EasyValueTask.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private WaitLock connectWaitLock = new(nameof(DeviceBase));
 | 
			
		||||
 | 
			
		||||
    public async Task ConnectAsync(CancellationToken token)
 | 
			
		||||
    public async ValueTask ConnectAsync(CancellationToken token)
 | 
			
		||||
    {
 | 
			
		||||
        if (AutoConnect && Channel != null && Channel?.Online != true)
 | 
			
		||||
        {
 | 
			
		||||
@@ -535,8 +533,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
        int timeout = 3000,
 | 
			
		||||
        CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
 | 
			
		||||
        command.Sign = sign;
 | 
			
		||||
        WaitLock? waitLock = null;
 | 
			
		||||
@@ -556,19 +552,17 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
            if (waitData.Status == WaitDataStatus.Success)
 | 
			
		||||
                return waitData.CompletedData;
 | 
			
		||||
 | 
			
		||||
            bool timeoutStatus = false;
 | 
			
		||||
 | 
			
		||||
            var reusableTimeout = _reusableTimeouts.Get();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, Channel.ClosedToken);
 | 
			
		||||
 | 
			
		||||
                await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            catch (OperationCanceledException)
 | 
			
		||||
            {
 | 
			
		||||
                timeoutStatus = reusableTimeout.TimeoutStatus;
 | 
			
		||||
                return timeoutStatus
 | 
			
		||||
                return reusableTimeout.TimeoutStatus
 | 
			
		||||
                    ? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" }
 | 
			
		||||
                    : new MessageBase(new OperationCanceledException());
 | 
			
		||||
            }
 | 
			
		||||
@@ -579,7 +573,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                reusableTimeout.Set();
 | 
			
		||||
                timeoutStatus = reusableTimeout.TimeoutStatus;
 | 
			
		||||
                _reusableTimeouts.Return(reusableTimeout);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -589,7 +582,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var operResult = waitData.Check(timeoutStatus);
 | 
			
		||||
                var operResult = waitData.Check(reusableTimeout.TimeoutStatus);
 | 
			
		||||
                return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -467,5 +467,5 @@ public interface IDevice : IDisposable, IDisposableObject, IAsyncDisposable
 | 
			
		||||
    /// <param name="deviceLog">单独设备日志</param>
 | 
			
		||||
    void InitChannel(IChannel channel, ILog? deviceLog = null);
 | 
			
		||||
    ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default);
 | 
			
		||||
    Task ConnectAsync(CancellationToken token);
 | 
			
		||||
    ValueTask ConnectAsync(CancellationToken token);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@
 | 
			
		||||
    "RemoteUrl": "RemoteUrl",
 | 
			
		||||
    "RtsEnable": "RtsEnable",
 | 
			
		||||
    "StreamAsync": "StreamAsync",
 | 
			
		||||
    "Handshake": "Handshake",
 | 
			
		||||
    "StopBits": "StopBits"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Foundation.ConverterConfig": {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,8 @@
 | 
			
		||||
    "PortName": "COM口",
 | 
			
		||||
    "RemoteUrl": "远程url",
 | 
			
		||||
    "RtsEnable": "Rts",
 | 
			
		||||
    "StreamAsync": "串口流读写",
 | 
			
		||||
    "StreamAsync": "流读写",
 | 
			
		||||
    "Handshake": "Handshake",
 | 
			
		||||
    "StopBits": "停止位"
 | 
			
		||||
  },
 | 
			
		||||
  "ThingsGateway.Foundation.ConverterConfig": {
 | 
			
		||||
 
 | 
			
		||||
@@ -145,6 +145,7 @@ public class TextFileLogger : ThingsGateway.NewLife.Log.TextFileLog, TouchSocket
 | 
			
		||||
        }
 | 
			
		||||
        WriteLog(logLevel, source, message, exception);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override void Dispose(bool disposing)
 | 
			
		||||
    {
 | 
			
		||||
        cache.Remove(CacheKey);
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ using System.Text;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.String;
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Resources;
 | 
			
		||||
 | 
			
		||||
@@ -72,7 +73,7 @@ public partial class ThingsGatewayBitConverter : IThingsGatewayBitConverter
 | 
			
		||||
        get => endianType; set
 | 
			
		||||
        {
 | 
			
		||||
            endianType = value;
 | 
			
		||||
            TouchSocketBitConverter = new TouchSocketBitConverter(endianType);
 | 
			
		||||
            TouchSocketBitConverter = TouchSocketBitConverter.GetBitConverter(endianType);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -187,14 +188,13 @@ public partial class ThingsGatewayBitConverter : IThingsGatewayBitConverter
 | 
			
		||||
 | 
			
		||||
        // 更新设备地址为去除附加信息后的地址
 | 
			
		||||
        registerAddress = sb.ToString();
 | 
			
		||||
 | 
			
		||||
        var converter = (IThingsGatewayBitConverter)this!.Map(type);
 | 
			
		||||
        // 如果没有解析出任何附加信息,则直接返回默认的数据转换器
 | 
			
		||||
        if (bcdFormat == null && stringlength == null && encoding == null && dataFormat == null && wstring == null)
 | 
			
		||||
        {
 | 
			
		||||
            //MemoryCache.Set(cacheKey, this!, 3600);
 | 
			
		||||
            return converter;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        var converter = (IThingsGatewayBitConverter)FastMapper.Mapper(this, type);
 | 
			
		||||
 | 
			
		||||
        // 根据默认的数据转换器创建新的数据转换器实例
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,14 +18,20 @@ public static class JSRuntimeExtensions
 | 
			
		||||
        {
 | 
			
		||||
            await jsRuntime.InvokeVoidAsync("BlazorDiagrams.observe", element, reference, element.Id).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch (ObjectDisposedException)
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
            // Ignore, DotNetObjectReference was likely disposed
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementReference element)
 | 
			
		||||
    {
 | 
			
		||||
        await jsRuntime.InvokeVoidAsync("BlazorDiagrams.unobserve", element, element.Id).ConfigureAwait(false);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await jsRuntime.InvokeVoidAsync("BlazorDiagrams.unobserve", element, element.Id).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,6 @@ using ThingsGateway.NewLife.Json.Extension;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Core;
 | 
			
		||||
 | 
			
		||||
using static System.Net.Mime.MediaTypeNames;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Gateway.Application;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -378,6 +378,8 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
 | 
			
		||||
                    // 记录设备线程已停止的信息
 | 
			
		||||
                    LogMessage?.LogInformation(string.Format(AppResource.DeviceTaskStop, DeviceName));
 | 
			
		||||
 | 
			
		||||
                    await Task.Delay(50).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    // 执行资源释放操作
 | 
			
		||||
                    await this.SafeDisposeAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -142,7 +142,14 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "StreamAsync", IsNullable = true)]
 | 
			
		||||
    [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
 | 
			
		||||
    public override bool StreamAsync { get; set; } = false;
 | 
			
		||||
    public override bool StreamAsync { get; set; }
 | 
			
		||||
    
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Handshake
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "Handshake", IsNullable = true)]
 | 
			
		||||
    [AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
 | 
			
		||||
    public override Handshake Handshake { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 缓存超时
 | 
			
		||||
 
 | 
			
		||||
@@ -121,7 +121,7 @@ public class Device : BaseDataEntity, IValidatableObject
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "冗余扫描间隔")]
 | 
			
		||||
    [AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
 | 
			
		||||
    [MinValue(30000)]
 | 
			
		||||
    [MinValue(10000)]
 | 
			
		||||
    public virtual int RedundantScanIntervalTime { get; set; } = 30000;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -259,7 +259,15 @@ public static class GlobalData
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static bool IsRedundantEnable(long deviceId)
 | 
			
		||||
    {
 | 
			
		||||
        if (GlobalData.IdDevices.TryGetValue(deviceId, out var deviceRuntime))
 | 
			
		||||
        {
 | 
			
		||||
            if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null)
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    public static IEnumerable<VariableRuntime> GetEnableVariables()
 | 
			
		||||
    {
 | 
			
		||||
        return IdVariables.Where(a => a.Value.DeviceRuntime?.Enable != false && a.Value.DeviceRuntime?.ChannelRuntime?.Enable != false && a.Value?.Enable == true).Select(a => a.Value);
 | 
			
		||||
 
 | 
			
		||||
@@ -308,6 +308,7 @@
 | 
			
		||||
    "RemoteUrl": "RemoteUrl",
 | 
			
		||||
    "RtsEnable": "RtsEnable",
 | 
			
		||||
    "StreamAsync": "StreamAsync",
 | 
			
		||||
    "Handshake": "Handshake",
 | 
			
		||||
    "SaveChannel": "Add/Modify Channel",
 | 
			
		||||
    "SortCode": "SortCode",
 | 
			
		||||
    "StopBits": "StopBits",
 | 
			
		||||
 
 | 
			
		||||
@@ -306,7 +306,8 @@
 | 
			
		||||
    "PortName": "COM口",
 | 
			
		||||
    "RemoteUrl": "远程url",
 | 
			
		||||
    "RtsEnable": "Rts",
 | 
			
		||||
    "StreamAsync": "串口流读写",
 | 
			
		||||
    "StreamAsync": "流读写",
 | 
			
		||||
    "Handshake": "Handshake",
 | 
			
		||||
    "SaveChannel": "添加/修改通道",
 | 
			
		||||
    "SortCode": "排序",
 | 
			
		||||
    "StopBits": "停止位",
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,7 @@ public class ChannelRuntime : Channel
 | 
			
		||||
    [Newtonsoft.Json.JsonIgnore]
 | 
			
		||||
    [MapperIgnore]
 | 
			
		||||
    [AutoGenerateColumn(Ignore = true)]
 | 
			
		||||
    internal ConcurrentDictionary<long, DeviceRuntime>? DeviceRuntimes { get; } = new(Environment.ProcessorCount, 1000);
 | 
			
		||||
    internal ConcurrentDictionary<long, DeviceRuntime>? DeviceRuntimes { get; } = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设备数量
 | 
			
		||||
 
 | 
			
		||||
@@ -251,7 +251,7 @@ public class DeviceRuntime : Device
 | 
			
		||||
    [Newtonsoft.Json.JsonIgnore]
 | 
			
		||||
    [MapperIgnore]
 | 
			
		||||
    [AutoGenerateColumn(Ignore = true)]
 | 
			
		||||
    internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; } = new(Environment.ProcessorCount, 1000);
 | 
			
		||||
    internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; } = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 特殊方法变量
 | 
			
		||||
 
 | 
			
		||||
@@ -233,7 +233,7 @@
 | 
			
		||||
      "Code": "System",
 | 
			
		||||
      "Category": "1",
 | 
			
		||||
      "Target": "0",
 | 
			
		||||
      "NavLinkMatch": "1",
 | 
			
		||||
      "NavLinkMatch": "0",
 | 
			
		||||
      "CreateTime": "2025-01-14 20:16:18.362",
 | 
			
		||||
      "CreateUserId": "0",
 | 
			
		||||
      "IsDelete": "0",
 | 
			
		||||
@@ -248,7 +248,7 @@
 | 
			
		||||
      "Code": "System",
 | 
			
		||||
      "Category": "1",
 | 
			
		||||
      "Target": "0",
 | 
			
		||||
      "NavLinkMatch": "1",
 | 
			
		||||
      "NavLinkMatch": "0",
 | 
			
		||||
      "CreateTime": "2025-01-14 20:16:18.362",
 | 
			
		||||
      "CreateUserId": "0",
 | 
			
		||||
      "IsDelete": "0",
 | 
			
		||||
 
 | 
			
		||||
@@ -177,7 +177,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
 | 
			
		||||
            {
 | 
			
		||||
                await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
 | 
			
		||||
                await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
@@ -209,7 +209,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
 | 
			
		||||
            {
 | 
			
		||||
                await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
 | 
			
		||||
                await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
@@ -241,7 +241,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
 | 
			
		||||
            {
 | 
			
		||||
                await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
 | 
			
		||||
                await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,11 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
 | 
			
		||||
 | 
			
		||||
            var device = devices.Keys.ToList();
 | 
			
		||||
            ManageHelper.CheckDeviceCount(device.Count);
 | 
			
		||||
 | 
			
		||||
            foreach (var item in device)
 | 
			
		||||
            {
 | 
			
		||||
                item.RedundantEnable = false;
 | 
			
		||||
                item.RedundantDeviceId = null;
 | 
			
		||||
            }
 | 
			
		||||
            await db.Insertable(device).ExecuteCommandAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var variable = devices.SelectMany(a => a.Value).ToList();
 | 
			
		||||
 
 | 
			
		||||
@@ -205,7 +205,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService
 | 
			
		||||
            if (restart)
 | 
			
		||||
            {
 | 
			
		||||
                await RuntimeServiceHelper.RestartDeviceAsync(newDeviceRuntimes).ConfigureAwait(false);
 | 
			
		||||
                await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
 | 
			
		||||
                await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,11 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
 | 
			
		||||
        {
 | 
			
		||||
            var device = devices.Keys.ToList();
 | 
			
		||||
            ManageHelper.CheckDeviceCount(device.Count);
 | 
			
		||||
 | 
			
		||||
            foreach (var item in device)
 | 
			
		||||
            {
 | 
			
		||||
                item.RedundantEnable = false;
 | 
			
		||||
                item.RedundantDeviceId = null;
 | 
			
		||||
            }
 | 
			
		||||
            await db.Insertable(device).ExecuteCommandAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var variable = devices.SelectMany(a => a.Value).ToList();
 | 
			
		||||
@@ -263,6 +267,9 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
 | 
			
		||||
        else
 | 
			
		||||
            ManageHelper.CheckDeviceCount(1);
 | 
			
		||||
 | 
			
		||||
        if (input.RedundantEnable && GlobalData.IsRedundantEnable(input.RedundantDeviceId ?? 0))
 | 
			
		||||
            throw Oops.Bah($"Redundancy configuration error, backup device has been planned into another redundancy group");
 | 
			
		||||
 | 
			
		||||
        if (await base.SaveAsync(input, type).ConfigureAwait(false))
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -330,6 +330,12 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
 | 
			
		||||
                        driver.IsInitSuccess = false;
 | 
			
		||||
                    LogMessage?.LogWarning(ex, string.Format(AppResource.InitFail, CurrentChannel.PluginName, driver?.DeviceName));
 | 
			
		||||
                }
 | 
			
		||||
                if (driver == null)
 | 
			
		||||
                {
 | 
			
		||||
                    LogMessage?.LogWarning(string.Format(AppResource.InitFail, CurrentChannel.PluginName, driver?.DeviceName));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (driver?.DeviceId > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    if (CancellationTokenSources.TryGetValue(driver.DeviceId, out var oldCts))
 | 
			
		||||
@@ -814,6 +820,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
 | 
			
		||||
 | 
			
		||||
            LogMessage?.LogInformation(string.Format(AppResource.ChannelDispose, CurrentChannel?.Name ?? string.Empty));
 | 
			
		||||
 | 
			
		||||
            await Task.Delay(50).ConfigureAwait(false);
 | 
			
		||||
            LogMessage?.Logs?.ForEach(a => a.TryDispose());
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
 
 | 
			
		||||
@@ -239,7 +239,7 @@ internal static class RuntimeServiceHelper
 | 
			
		||||
            if (group.Key != null)
 | 
			
		||||
                await group.Key.RestartDeviceAsync(group.Value, false).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
 | 
			
		||||
        foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
 | 
			
		||||
        {
 | 
			
		||||
            if (group.Key != null)
 | 
			
		||||
                await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
 | 
			
		||||
@@ -260,7 +260,7 @@ internal static class RuntimeServiceHelper
 | 
			
		||||
                await group.Key.RemoveDeviceAsync(group.Value.Select(a => a.Id).ToArray()).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
 | 
			
		||||
        foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !deviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
 | 
			
		||||
        {
 | 
			
		||||
            if (group.Key != null)
 | 
			
		||||
                await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
 | 
			
		||||
@@ -268,9 +268,9 @@ internal static class RuntimeServiceHelper
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task ChangedDriverAsync(ILogger logger)
 | 
			
		||||
    public static async Task ChangedDriverAsync(DeviceRuntime[] channelDevice, ILogger logger)
 | 
			
		||||
    {
 | 
			
		||||
        var channelDevice = GlobalData.GetAllVariableBusinessDeviceRuntime();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        await channelDevice.ParallelForEachAsync(async (item, token) =>
 | 
			
		||||
         {
 | 
			
		||||
 
 | 
			
		||||
@@ -398,22 +398,22 @@ public class VariableRuntimeService : IVariableRuntimeService
 | 
			
		||||
                //批量修改之后,需要重新加载通道
 | 
			
		||||
                RuntimeServiceHelper.Init(newChannelRuntimes);
 | 
			
		||||
 | 
			
		||||
                {
 | 
			
		||||
                    var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
 | 
			
		||||
 | 
			
		||||
                    RuntimeServiceHelper.Init(newDeviceRuntimes);
 | 
			
		||||
                }
 | 
			
		||||
                {
 | 
			
		||||
                    var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
 | 
			
		||||
                    RuntimeServiceHelper.Init(newVariableRuntimes);
 | 
			
		||||
                }
 | 
			
		||||
                var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
 | 
			
		||||
 | 
			
		||||
                RuntimeServiceHelper.Init(newDeviceRuntimes);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
 | 
			
		||||
                RuntimeServiceHelper.Init(newVariableRuntimes);
 | 
			
		||||
 | 
			
		||||
                //根据条件重启通道线程
 | 
			
		||||
 | 
			
		||||
                if (restart)
 | 
			
		||||
                {
 | 
			
		||||
                    await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
 | 
			
		||||
                    await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -437,22 +437,21 @@ public class VariableRuntimeService : IVariableRuntimeService
 | 
			
		||||
                //批量修改之后,需要重新加载通道
 | 
			
		||||
                RuntimeServiceHelper.Init(newChannelRuntimes);
 | 
			
		||||
 | 
			
		||||
                {
 | 
			
		||||
                    var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
 | 
			
		||||
 | 
			
		||||
                    RuntimeServiceHelper.Init(newDeviceRuntimes);
 | 
			
		||||
                }
 | 
			
		||||
                {
 | 
			
		||||
                    var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
 | 
			
		||||
                    RuntimeServiceHelper.Init(newVariableRuntimes);
 | 
			
		||||
                }
 | 
			
		||||
                var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
 | 
			
		||||
 | 
			
		||||
                RuntimeServiceHelper.Init(newDeviceRuntimes);
 | 
			
		||||
 | 
			
		||||
                var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
 | 
			
		||||
                RuntimeServiceHelper.Init(newVariableRuntimes);
 | 
			
		||||
 | 
			
		||||
                //根据条件重启通道线程
 | 
			
		||||
 | 
			
		||||
                if (restart)
 | 
			
		||||
                {
 | 
			
		||||
                    await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
 | 
			
		||||
                    await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,7 @@
 | 
			
		||||
        <EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
        <EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
        <EditorItem @bind-Field="@context.StreamAsync" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
        <EditorItem @bind-Field="@context.Handshake" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        <EditorItem @bind-Field="@context.CacheTimeout" Ignore=@(context.ChannelType == ChannelTypeEnum.UdpSession || context.ChannelType == ChannelTypeEnum.Other) />
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,8 @@
 | 
			
		||||
                        <EditorItem @bind-Field="@context.StopBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.StreamAsync" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.StreamAsync" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
 | 
			
		||||
                        <EditorItem @bind-Field="@context.Handshake" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        <EditorItem @bind-Field="@context.CacheTimeout" Ignore=@(context.ChannelType==ChannelTypeEnum.UdpSession||context.ChannelType==ChannelTypeEnum.Other) />
 | 
			
		||||
 
 | 
			
		||||
@@ -1319,17 +1319,26 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
 | 
			
		||||
    private TreeViewItem<ChannelDeviceTreeItem> UnknownTreeViewItem;
 | 
			
		||||
 | 
			
		||||
    SmartTriggerScheduler? scheduler;
 | 
			
		||||
 | 
			
		||||
    private bool _initialized;
 | 
			
		||||
    public override async Task SetParametersAsync(ParameterView parameters)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        parameters.SetParameterProperties(this);
 | 
			
		||||
        OnInitialized();
 | 
			
		||||
        await OnInitializedAsync();
 | 
			
		||||
        OnParametersSet();
 | 
			
		||||
        StateHasChanged();
 | 
			
		||||
        await OnParametersSetAsync();
 | 
			
		||||
        if (!_initialized)
 | 
			
		||||
        {
 | 
			
		||||
            _initialized = true;
 | 
			
		||||
 | 
			
		||||
            OnInitialized();
 | 
			
		||||
            await OnInitializedAsync();
 | 
			
		||||
            OnParametersSet();
 | 
			
		||||
            StateHasChanged();
 | 
			
		||||
            await OnParametersSetAsync();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            OnParametersSet();
 | 
			
		||||
            StateHasChanged();
 | 
			
		||||
            await OnParametersSetAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override async Task OnInitializedAsync()
 | 
			
		||||
@@ -1402,11 +1411,11 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
 | 
			
		||||
 | 
			
		||||
        await base.OnInitializedAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task Notify(CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (cancellationToken.IsCancellationRequested) return;
 | 
			
		||||
        if (Disposed) return;
 | 
			
		||||
 | 
			
		||||
        await OnClickSearch(SearchText);
 | 
			
		||||
 | 
			
		||||
        Value = GetValue(Value);
 | 
			
		||||
@@ -1415,6 +1424,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
 | 
			
		||||
            await ChannelDeviceChanged.Invoke(Value);
 | 
			
		||||
        }
 | 
			
		||||
        await InvokeAsync(StateHasChanged);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ChannelDeviceTreeItem GetValue(ChannelDeviceTreeItem channelDeviceTreeItem)
 | 
			
		||||
 
 | 
			
		||||
@@ -120,6 +120,7 @@ public partial class DeviceEditComponent
 | 
			
		||||
 | 
			
		||||
    private string ChannelName;
 | 
			
		||||
    private string DeviceName;
 | 
			
		||||
    private bool _initialized;
 | 
			
		||||
    public override async Task SetParametersAsync(ParameterView parameters)
 | 
			
		||||
    {
 | 
			
		||||
        if (ChannelName.IsNullOrEmpty())
 | 
			
		||||
@@ -127,11 +128,22 @@ public partial class DeviceEditComponent
 | 
			
		||||
            parameters.SetParameterProperties(this);
 | 
			
		||||
            ChannelName = await ChannelPageService.GetChannelNameAsync(Model?.ChannelId ?? 0);
 | 
			
		||||
            DeviceName = await DevicePageService.GetDeviceNameAsync(Model?.RedundantDeviceId ?? 0);
 | 
			
		||||
            OnInitialized();
 | 
			
		||||
            await OnInitializedAsync();
 | 
			
		||||
            OnParametersSet();
 | 
			
		||||
            StateHasChanged();
 | 
			
		||||
            await OnParametersSetAsync();
 | 
			
		||||
            if (!_initialized)
 | 
			
		||||
            {
 | 
			
		||||
                _initialized = true;
 | 
			
		||||
 | 
			
		||||
                OnInitialized();
 | 
			
		||||
                await OnInitializedAsync();
 | 
			
		||||
                OnParametersSet();
 | 
			
		||||
                StateHasChanged();
 | 
			
		||||
                await OnParametersSetAsync();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                OnParametersSet();
 | 
			
		||||
                StateHasChanged();
 | 
			
		||||
                await OnParametersSetAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,11 @@ public partial class VariableEditComponent
 | 
			
		||||
    private string DeviceName;
 | 
			
		||||
    private string ChoiceBusinessDeviceName;
 | 
			
		||||
    private bool first = false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private bool _initialized;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public override async Task SetParametersAsync(ParameterView parameters)
 | 
			
		||||
    {
 | 
			
		||||
        if (DeviceName.IsNullOrEmpty() && first == false)
 | 
			
		||||
@@ -56,14 +61,31 @@ public partial class VariableEditComponent
 | 
			
		||||
            first = true;
 | 
			
		||||
            parameters.SetParameterProperties(this);
 | 
			
		||||
            DeviceName = await DevicePageService.GetDeviceNameAsync(Model?.DeviceId ?? 0);
 | 
			
		||||
            OnInitialized();
 | 
			
		||||
            await OnInitializedAsync();
 | 
			
		||||
            OnParametersSet();
 | 
			
		||||
            if (!_initialized)
 | 
			
		||||
            {
 | 
			
		||||
                _initialized = true;
 | 
			
		||||
 | 
			
		||||
            ChoiceBusinessDeviceId = ChoiceBusinessDeviceId > 0 ? ChoiceBusinessDeviceId : (await DevicePageService.OnDeviceSelectedItemQueryAsync(new VirtualizeQueryOption() { Count = 1 }, false).ConfigureAwait(false)).Items.FirstOrDefault()?.Value?.ToLong() ?? 0;
 | 
			
		||||
            ChoiceBusinessDeviceName = (await DevicePageService.GetDeviceNameAsync(ChoiceBusinessDeviceId)) ?? string.Empty;
 | 
			
		||||
                OnInitialized();
 | 
			
		||||
                await OnInitializedAsync();
 | 
			
		||||
                OnParametersSet();
 | 
			
		||||
 | 
			
		||||
                ChoiceBusinessDeviceId = ChoiceBusinessDeviceId > 0 ? ChoiceBusinessDeviceId : (await DevicePageService.OnDeviceSelectedItemQueryAsync(new VirtualizeQueryOption() { Count = 1 }, false).ConfigureAwait(false)).Items.FirstOrDefault()?.Value?.ToLong() ?? 0;
 | 
			
		||||
                ChoiceBusinessDeviceName = (await DevicePageService.GetDeviceNameAsync(ChoiceBusinessDeviceId)) ?? string.Empty;
 | 
			
		||||
 | 
			
		||||
                await InvokeAsync(StateHasChanged);
 | 
			
		||||
                await OnParametersSetAsync();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                OnParametersSet();
 | 
			
		||||
 | 
			
		||||
                ChoiceBusinessDeviceId = ChoiceBusinessDeviceId > 0 ? ChoiceBusinessDeviceId : (await DevicePageService.OnDeviceSelectedItemQueryAsync(new VirtualizeQueryOption() { Count = 1 }, false).ConfigureAwait(false)).Items.FirstOrDefault()?.Value?.ToLong() ?? 0;
 | 
			
		||||
                ChoiceBusinessDeviceName = (await DevicePageService.GetDeviceNameAsync(ChoiceBusinessDeviceId)) ?? string.Empty;
 | 
			
		||||
                await InvokeAsync(StateHasChanged);
 | 
			
		||||
 | 
			
		||||
                await OnParametersSetAsync();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await InvokeAsync(StateHasChanged);
 | 
			
		||||
 | 
			
		||||
            await OnParametersSetAsync();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,13 @@
 | 
			
		||||
 | 
			
		||||
<div class="rule-t text-end row g-0 pb-2">
 | 
			
		||||
 | 
			
		||||
    <ButtonUpload class="col-auto ms-auto me-2" Size="Size.Small" ShowUploadFileList="false" BrowserButtonText=@(Localizer["Load"]) IsMultiple="false" OnChange="@Load" TValue="string" Accept=".json" />
 | 
			
		||||
    <ButtonUpload class="col-auto ms-auto me-2" Size="Size.Small" ShowUploadFileList="false" BrowserButtonText=@(Localizer["Load"]) IsMultiple="true" OnChange="@Load" TValue="string" Accept=".json" />
 | 
			
		||||
 | 
			
		||||
    <Button class="col-auto me-2" Size="Size.Small" Text=@(Localizer["Download"]) OnClick="Download" />
 | 
			
		||||
 | 
			
		||||
    <Button class="col-auto me-2" Size="Size.Small" Text=@(Localizer["Save"]) OnClick="OnSave" />
 | 
			
		||||
    <Button class="col-auto me-2" Size="Size.Small" Text=@(Localizer["Cancel"]) OnClick="OnCancel" />
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="row g-0 rule-b rulesengine">
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,8 @@ public partial class DragAndDrop
 | 
			
		||||
        {
 | 
			
		||||
            var data = await upload.GetBytesAsync(1024 * 1024);
 | 
			
		||||
            var str = Encoding.UTF8.GetString(data);
 | 
			
		||||
            await Load(str.FromJsonNetString<RulesJson>());
 | 
			
		||||
            Load(str.FromJsonNetString<RulesJson>());
 | 
			
		||||
            Load(Value);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -92,21 +93,16 @@ public partial class DragAndDrop
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override async Task OnParametersSetAsync()
 | 
			
		||||
    protected override void OnParametersSet()
 | 
			
		||||
    {
 | 
			
		||||
        await Load(Value);
 | 
			
		||||
        Load(Value);
 | 
			
		||||
    }
 | 
			
		||||
    public async Task Load(RulesJson value)
 | 
			
		||||
    private void Load(RulesJson value)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Value = value;
 | 
			
		||||
            RuleHelpers.Load(_blazorDiagram, Value);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            await ToastService.Warn(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Value = value;
 | 
			
		||||
        RuleHelpers.Load(_blazorDiagram, Value);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Inject]
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,8 @@ public partial class RulesPage : ThingsGatewayModuleComponentBase
 | 
			
		||||
    protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
			
		||||
    {
 | 
			
		||||
        await base.OnAfterRenderAsync(firstRender);
 | 
			
		||||
        if (firstRender) {
 | 
			
		||||
        if (firstRender)
 | 
			
		||||
        {
 | 
			
		||||
            StateHasChanged();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
                <Button OnClick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
 | 
			
		||||
            }
 | 
			
		||||
            @* 版本号 *@
 | 
			
		||||
            <div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
 | 
			
		||||
            <div class="px-1 navbar-header-text text-nowrap d-none d-lg-block">@_versionString</div>
 | 
			
		||||
 | 
			
		||||
            @* 主题切换 *@
 | 
			
		||||
            @* <ThemeToggle /> *@
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,163 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://kimdiego2098.github.io/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using BenchmarkDotNet.Diagnosers;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation;
 | 
			
		||||
 | 
			
		||||
using BenchmarkDotNet.Attributes;
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Core;
 | 
			
		||||
 | 
			
		||||
public class Foo
 | 
			
		||||
{
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
    public int Age { get; set; }
 | 
			
		||||
    public int Age1 { get; set; }
 | 
			
		||||
    public int Age2 { get; set; }
 | 
			
		||||
    public int Age3 { get; set; }
 | 
			
		||||
    public int Age11 { get; set; }
 | 
			
		||||
    public int Age21 { get; set; }
 | 
			
		||||
    public int Age31 { get; set; }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class Bar
 | 
			
		||||
{
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
    public int Age { get; set; }
 | 
			
		||||
    public Bar Child { get; set; }
 | 
			
		||||
    public int Age1 { get; set; }
 | 
			
		||||
    public int Age2 { get; set; }
 | 
			
		||||
    public int Age3 { get; set; }
 | 
			
		||||
    public int Age31 { get; set; }
 | 
			
		||||
    public int Age32 { get; set; }
 | 
			
		||||
    public int Age33 { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[RankColumn]
 | 
			
		||||
[MemoryDiagnoser]
 | 
			
		||||
public class MapperBench
 | 
			
		||||
{
 | 
			
		||||
    private List<Foo> foos;
 | 
			
		||||
 | 
			
		||||
    [GlobalSetup]
 | 
			
		||||
    public void Setup()
 | 
			
		||||
    {
 | 
			
		||||
        const int N = 100_000;
 | 
			
		||||
        foos = new List<Foo>(N);
 | 
			
		||||
        for (int i = 0; i < N; i++)
 | 
			
		||||
        {
 | 
			
		||||
            foos.Add(new Foo
 | 
			
		||||
            {
 | 
			
		||||
                Name = $"Name{i}",
 | 
			
		||||
                Age = i,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Benchmark(Baseline = true)]
 | 
			
		||||
    public List<Bar> ManualConstructionMap100_000()
 | 
			
		||||
    {
 | 
			
		||||
        var bars = new List<Bar>(foos.Count);
 | 
			
		||||
        foreach (var f in foos)
 | 
			
		||||
        {
 | 
			
		||||
            bars.Add(new Bar
 | 
			
		||||
            {
 | 
			
		||||
                Name = f.Name,
 | 
			
		||||
                Age = f.Age,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return bars;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Benchmark]
 | 
			
		||||
    public List<Bar> ReflectionMap100_000()
 | 
			
		||||
    {
 | 
			
		||||
        var bars = new List<Bar>(foos.Count);
 | 
			
		||||
        foreach (var f in foos)
 | 
			
		||||
        {
 | 
			
		||||
            bars.Add(OriginalMapper(f));
 | 
			
		||||
        }
 | 
			
		||||
        return bars;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Benchmark]
 | 
			
		||||
    public List<Bar> FastMapper100_000()
 | 
			
		||||
    {
 | 
			
		||||
        var bars = new List<Bar>(foos.Count);
 | 
			
		||||
        foreach (var f in foos)
 | 
			
		||||
        {
 | 
			
		||||
            bars.Add(FastMapper.Mapper<Foo, Bar>(f));
 | 
			
		||||
        }
 | 
			
		||||
        return bars;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Benchmark]
 | 
			
		||||
    public List<Bar> ReflectionCachedMap100_000()
 | 
			
		||||
    {
 | 
			
		||||
        var bars = new List<Bar>(foos.Count);
 | 
			
		||||
        foreach (var f in foos)
 | 
			
		||||
        {
 | 
			
		||||
            bars.Add(f.Map<Bar>());
 | 
			
		||||
        }
 | 
			
		||||
        return bars;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static Bar OriginalMapper(Foo source)
 | 
			
		||||
    {
 | 
			
		||||
        if (source == null) return null;
 | 
			
		||||
 | 
			
		||||
        var target = new Bar();
 | 
			
		||||
        var st = typeof(Foo).GetProperties();
 | 
			
		||||
        var tt = typeof(Bar).GetProperties();
 | 
			
		||||
 | 
			
		||||
        foreach (var sp in st)
 | 
			
		||||
        {
 | 
			
		||||
            var tp = Array.Find(tt, x => x.Name == sp.Name);
 | 
			
		||||
            if (tp == null || !tp.CanWrite) continue;
 | 
			
		||||
 | 
			
		||||
            var value = sp.GetValue(source);
 | 
			
		||||
 | 
			
		||||
            if (value == null)
 | 
			
		||||
            {
 | 
			
		||||
                tp.SetValue(target, null);
 | 
			
		||||
            }
 | 
			
		||||
            else if (IsSimpleType(sp.PropertyType))
 | 
			
		||||
            {
 | 
			
		||||
                // 基础类型直接赋值
 | 
			
		||||
                tp.SetValue(target, value);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                // 嵌套对象递归映射
 | 
			
		||||
                tp.SetValue(target, value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return target;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static bool IsSimpleType(Type type)
 | 
			
		||||
    {
 | 
			
		||||
        return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -8,11 +8,8 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using BenchmarkConsoleApp;
 | 
			
		||||
 | 
			
		||||
using BenchmarkDotNet.Attributes;
 | 
			
		||||
using BenchmarkDotNet.Diagnosers;
 | 
			
		||||
using BenchmarkDotNet.Jobs;
 | 
			
		||||
 | 
			
		||||
using Longbow.Modbus;
 | 
			
		||||
using Longbow.TcpSocket;
 | 
			
		||||
@@ -31,11 +28,15 @@ using ModbusMaster = ThingsGateway.Foundation.Modbus.ModbusMaster;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation;
 | 
			
		||||
 | 
			
		||||
[SimpleJob(RuntimeMoniker.Net80)]
 | 
			
		||||
[SimpleJob(RuntimeMoniker.Net10_0)]
 | 
			
		||||
//[SimpleJob(RuntimeMoniker.Net80)]
 | 
			
		||||
//[SimpleJob(RuntimeMoniker.Net10_0)]
 | 
			
		||||
[MemoryDiagnoser]
 | 
			
		||||
public class ModbusBenchmark : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    public static int ClientCount = 1;
 | 
			
		||||
    public static int TaskNumberOfItems = 1;
 | 
			
		||||
    public static int NumberOfItems = 10;
 | 
			
		||||
 | 
			
		||||
    private readonly List<IModbusClient> _lgbModbusClients = [];
 | 
			
		||||
    private List<ModbusMaster> thingsgatewaymodbuss = new();
 | 
			
		||||
    private List<IModbusMaster> nmodbuss = new();
 | 
			
		||||
@@ -45,7 +46,7 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
    [GlobalSetup]
 | 
			
		||||
    public async Task Init()
 | 
			
		||||
    {
 | 
			
		||||
        for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
        for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            var clientConfig = new TouchSocket.Core.TouchSocketConfig();
 | 
			
		||||
@@ -57,23 +58,23 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
                ModbusType = ModbusTypeEnum.ModbusTcp,
 | 
			
		||||
            };
 | 
			
		||||
            thingsgatewaymodbus.InitChannel(clientChannel);
 | 
			
		||||
         await   clientChannel.SetupAsync(clientChannel.Config);
 | 
			
		||||
            await clientChannel.SetupAsync(clientChannel.Config);
 | 
			
		||||
            clientChannel.Logger.LogLevel = LogLevel.Warning;
 | 
			
		||||
         await   thingsgatewaymodbus.ConnectAsync(CancellationToken.None);
 | 
			
		||||
        await    thingsgatewaymodbus.ReadAsync("40001", 100);
 | 
			
		||||
            await thingsgatewaymodbus.ConnectAsync(CancellationToken.None);
 | 
			
		||||
            await thingsgatewaymodbus.ModbusReadAsync(new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 });
 | 
			
		||||
            thingsgatewaymodbuss.Add(thingsgatewaymodbus);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
        for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            var factory = new NModbus.ModbusFactory();
 | 
			
		||||
            var nmodbus = factory.CreateMaster(new TcpClient("127.0.0.1", 502));
 | 
			
		||||
       await     nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
 | 
			
		||||
            await nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
 | 
			
		||||
            nmodbuss.Add(nmodbus);
 | 
			
		||||
        }
 | 
			
		||||
        //for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
        //for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
        //{
 | 
			
		||||
        //    ModbusTcpNet modbusTcpNet = new();
 | 
			
		||||
        //    modbusTcpNet.IpAddress = "127.0.0.1";
 | 
			
		||||
@@ -83,13 +84,13 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
        //    modbusTcpNets.Add(modbusTcpNet);
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
        for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
            var client = new ModbusTcpMaster();
 | 
			
		||||
      await      client.SetupAsync(new TouchSocketConfig()
 | 
			
		||||
    .SetRemoteIPHost("127.0.0.1:502"));
 | 
			
		||||
        await    client.ConnectAsync(CancellationToken.None);
 | 
			
		||||
      await      client.ReadHoldingRegistersAsync(0, 100);
 | 
			
		||||
            await client.SetupAsync(new TouchSocketConfig()
 | 
			
		||||
          .SetRemoteIPHost("127.0.0.1:502"));
 | 
			
		||||
            await client.ConnectAsync(CancellationToken.None);
 | 
			
		||||
            await client.ReadHoldingRegistersAsync(0, 100);
 | 
			
		||||
            modbusTcpMasters.Add(client);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -101,11 +102,11 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
            var provider = sc.BuildServiceProvider();
 | 
			
		||||
            var factory = provider.GetRequiredService<IModbusFactory>();
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
            for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var client = factory.GetOrCreateTcpMaster();
 | 
			
		||||
            await    client.ConnectAsync("127.0.0.1", 502);
 | 
			
		||||
             await   client.ReadHoldingRegistersAsync(0x01, 0x00, 10);
 | 
			
		||||
                await client.ConnectAsync("127.0.0.1", 502);
 | 
			
		||||
                await client.ReadHoldingRegistersAsync(0x01, 0x00, 10);
 | 
			
		||||
 | 
			
		||||
                _lgbModbusClients.Add(client);
 | 
			
		||||
            }
 | 
			
		||||
@@ -120,11 +121,11 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
        foreach (var thingsgatewaymodbus in thingsgatewaymodbuss)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
            for (int i = 0; i < TaskNumberOfItems; i++)
 | 
			
		||||
            {
 | 
			
		||||
                tasks.Add(Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    for (int i = 0; i < NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = await thingsgatewaymodbus.ModbusReadAsync(addr).ConfigureAwait(false);
 | 
			
		||||
                        if (!result.IsSuccess)
 | 
			
		||||
@@ -143,17 +144,22 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
    public async Task LongbowModbus()
 | 
			
		||||
    {
 | 
			
		||||
        List<Task> tasks = new List<Task>();
 | 
			
		||||
        foreach (var _lgbModbusClient in _lgbModbusClients)
 | 
			
		||||
        foreach (var client in _lgbModbusClients)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
            for (int i = 0; i < TaskNumberOfItems; i++)
 | 
			
		||||
            {
 | 
			
		||||
                tasks.Add(Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    for (int i = 0; i < NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        using var cts = new CancellationTokenSource(3000);
 | 
			
		||||
                        var task = await _lgbModbusClient.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false);
 | 
			
		||||
                        var result = await client.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false);
 | 
			
		||||
                        var data = result.ReadUShortValues(100);
 | 
			
		||||
                        if (!result.IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.Exception);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
@@ -167,11 +173,11 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
        List<Task> tasks = new List<Task>();
 | 
			
		||||
        foreach (var modbusTcpMaster in modbusTcpMasters)
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
            for (int i = 0; i < TaskNumberOfItems; i++)
 | 
			
		||||
            {
 | 
			
		||||
                tasks.Add(Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    for (int i = 0; i < NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = await modbusTcpMaster.ReadHoldingRegistersAsync(0, 100).ConfigureAwait(false);
 | 
			
		||||
                        var data = TouchSocketBitConverter.ConvertValues<byte, ushort>(result.Data.Span, EndianType.Little);
 | 
			
		||||
@@ -193,11 +199,11 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
        List<Task> tasks = new List<Task>();
 | 
			
		||||
        foreach (var nmodbus in nmodbuss)
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
            for (int i = 0; i < TaskNumberOfItems; i++)
 | 
			
		||||
            {
 | 
			
		||||
                tasks.Add(Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    for (int i = 0; i < NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = await nmodbus.ReadHoldingRegistersAsync(1, 0, 100).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -215,11 +221,11 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
    //    List<Task> tasks = new List<Task>();
 | 
			
		||||
    //    foreach (var modbusTcpNet in modbusTcpNets)
 | 
			
		||||
    //    {
 | 
			
		||||
    //        for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
    //        for (int i = 0; i < TaskNumberOfItems; i++)
 | 
			
		||||
    //        {
 | 
			
		||||
    //            tasks.Add(Task.Run(async () =>
 | 
			
		||||
    //            {
 | 
			
		||||
    //                for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
    //                for (int i = 0; i < NumberOfItems; i++)
 | 
			
		||||
    //                {
 | 
			
		||||
    //                    var result = await modbusTcpNet.ReadAsync("0", 100);
 | 
			
		||||
    //                    if (!result.IsSuccess)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,8 @@
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using BenchmarkConsoleApp;
 | 
			
		||||
 | 
			
		||||
using BenchmarkDotNet.Attributes;
 | 
			
		||||
using BenchmarkDotNet.Diagnosers;
 | 
			
		||||
using BenchmarkDotNet.Jobs;
 | 
			
		||||
 | 
			
		||||
using HslCommunication.Profinet.Siemens;
 | 
			
		||||
 | 
			
		||||
@@ -25,12 +22,14 @@ using TouchSocket.Core;
 | 
			
		||||
namespace ThingsGateway.Foundation;
 | 
			
		||||
 | 
			
		||||
//[SimpleJob(RuntimeMoniker.Net80)]
 | 
			
		||||
[SimpleJob(RuntimeMoniker.Net10_0)]
 | 
			
		||||
//[SimpleJob(RuntimeMoniker.Net10_0)]
 | 
			
		||||
[MemoryDiagnoser]
 | 
			
		||||
[BaselineColumn]
 | 
			
		||||
[RankColumn]
 | 
			
		||||
public class S7Benchmark : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    public static int ClientCount = 5;
 | 
			
		||||
    public static int TaskNumberOfItems = 1;
 | 
			
		||||
    public static int NumberOfItems = 5;
 | 
			
		||||
 | 
			
		||||
    private List<SiemensS7Master> siemensS7s = new();
 | 
			
		||||
 | 
			
		||||
    private List<Plc> plcs = new();
 | 
			
		||||
@@ -41,7 +40,7 @@ public class S7Benchmark : IDisposable
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
            for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var clientConfig = new TouchSocket.Core.TouchSocketConfig();
 | 
			
		||||
 | 
			
		||||
@@ -58,16 +57,16 @@ public class S7Benchmark : IDisposable
 | 
			
		||||
                await siemensS7.ReadAsync("M1", 100);
 | 
			
		||||
                siemensS7s.Add(siemensS7);
 | 
			
		||||
            }
 | 
			
		||||
            for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
            for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var siemensS7Net = new SiemensS7Net(SiemensPLCS.S1500, "127.0.0.1");
 | 
			
		||||
                await siemensS7Net.ConnectServerAsync();
 | 
			
		||||
                await siemensS7Net.ReadAsync("M0", 100);
 | 
			
		||||
                siemensS7Nets.Add(siemensS7Net);
 | 
			
		||||
            }
 | 
			
		||||
            for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
            for (int i = 0; i < ClientCount; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var plc = new Plc(CpuType.S7300, "127.0.0.1", 102, 0, 0);
 | 
			
		||||
                var plc = new Plc(CpuType.S71500, "127.0.0.1", 102, 0, 0);
 | 
			
		||||
                await plc.OpenAsync();//打开plc连接
 | 
			
		||||
                await plc.ReadAsync(DataType.Memory, 1, 0, VarType.Byte, 100);
 | 
			
		||||
                plcs.Add(plc);
 | 
			
		||||
@@ -81,11 +80,11 @@ public class S7Benchmark : IDisposable
 | 
			
		||||
        List<Task> tasks = new List<Task>();
 | 
			
		||||
        foreach (var plc in plcs)
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
            for (int i = 0; i < TaskNumberOfItems; i++)
 | 
			
		||||
            {
 | 
			
		||||
                tasks.Add(Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    for (int i = 0; i < NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = await plc.ReadAsync(DataType.Memory, 1, 0, VarType.Byte, 100);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -95,30 +94,29 @@ public class S7Benchmark : IDisposable
 | 
			
		||||
        await Task.WhenAll(tasks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //并发失败
 | 
			
		||||
    //[Benchmark]
 | 
			
		||||
    //public async Task HslCommunication()
 | 
			
		||||
    //{
 | 
			
		||||
    //    List<Task> tasks = new List<Task>();
 | 
			
		||||
    //    foreach (var siemensS7Net in siemensS7Nets)
 | 
			
		||||
    //    {
 | 
			
		||||
    //        for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
    //        {
 | 
			
		||||
    //            tasks.Add(Task.Run(async () =>
 | 
			
		||||
    //            {
 | 
			
		||||
    //                for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
    //                {
 | 
			
		||||
    //                    var result = await siemensS7Net.ReadAsync("M0", 100);
 | 
			
		||||
    //                    if (!result.IsSuccess)
 | 
			
		||||
    //                    {
 | 
			
		||||
    //                        throw new Exception(result.Message);
 | 
			
		||||
    //                    }
 | 
			
		||||
    //                }
 | 
			
		||||
    //            }));
 | 
			
		||||
    //        }
 | 
			
		||||
    //    }
 | 
			
		||||
    //    await Task.WhenAll(tasks);
 | 
			
		||||
    //}
 | 
			
		||||
    [Benchmark]
 | 
			
		||||
    public async Task HslCommunication()
 | 
			
		||||
    {
 | 
			
		||||
        List<Task> tasks = new List<Task>();
 | 
			
		||||
        foreach (var siemensS7Net in siemensS7Nets)
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < TaskNumberOfItems; i++)
 | 
			
		||||
            {
 | 
			
		||||
                tasks.Add(Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = await siemensS7Net.ReadAsync("M0", 100);
 | 
			
		||||
                        if (!result.IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            throw new Exception(result.Message);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        await Task.WhenAll(tasks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -132,11 +130,11 @@ public class S7Benchmark : IDisposable
 | 
			
		||||
        List<Task> tasks = new List<Task>();
 | 
			
		||||
        foreach (var siemensS7 in siemensS7s)
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
            for (int i = 0; i < TaskNumberOfItems; i++)
 | 
			
		||||
            {
 | 
			
		||||
                tasks.Add(Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    for (int i = 0; i < NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = await siemensS7.S7ReadAsync(siemensS7Address);
 | 
			
		||||
                        if (!result.IsSuccess)
 | 
			
		||||
 
 | 
			
		||||
@@ -20,14 +20,11 @@ namespace BenchmarkConsoleApp
 | 
			
		||||
{
 | 
			
		||||
    internal class Program
 | 
			
		||||
    {
 | 
			
		||||
        public static int ClientCount = 50;
 | 
			
		||||
        public static int TaskNumberOfItems = 6;
 | 
			
		||||
        public static int NumberOfItems = 50;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        private static async Task Main(string[] args)
 | 
			
		||||
        {
 | 
			
		||||
            Console.WriteLine("开始测试前,请先启动ModbusSlave,建议使用本项目自带的ThingsGateway.Debug.Photino软件开启,S7可以用KEPSERVER的S7模拟服务");
 | 
			
		||||
            Console.WriteLine($"多客户端({ClientCount}),多线程({TaskNumberOfItems})并发读取({NumberOfItems})测试,共{ClientCount * TaskNumberOfItems * NumberOfItems}次");
 | 
			
		||||
            await Task.CompletedTask;
 | 
			
		||||
            //ModbusBenchmark modbusBenchmark = new ModbusBenchmark();
 | 
			
		||||
            //System.Diagnostics.Stopwatch stopwatch = new();
 | 
			
		||||
@@ -45,6 +42,12 @@ namespace BenchmarkConsoleApp
 | 
			
		||||
            //ManualConfig.Create(DefaultConfig.Instance)
 | 
			
		||||
            //.WithOptions(ConfigOptions.DisableOptimizationsValidator)
 | 
			
		||||
            //);
 | 
			
		||||
 | 
			
		||||
            //            BenchmarkRunner.Run<MapperBench>(
 | 
			
		||||
            //ManualConfig.Create(DefaultConfig.Instance)
 | 
			
		||||
            //.WithOptions(ConfigOptions.DisableOptimizationsValidator)
 | 
			
		||||
            //);
 | 
			
		||||
 | 
			
		||||
            BenchmarkRunner.Run<ModbusBenchmark>(
 | 
			
		||||
       ManualConfig.Create(DefaultConfig.Instance)
 | 
			
		||||
           .WithOptions(ConfigOptions.DisableOptimizationsValidator)
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
 | 
			
		||||
		<PackageReference Include="HslCommunication" Version="12.5.1" />
 | 
			
		||||
		<PackageReference Include="Longbow.Modbus" Version="9.1.0" />
 | 
			
		||||
		<PackageReference Include="Longbow.Modbus" Version="9.1.1" />
 | 
			
		||||
		<PackageReference Include="NModbus" Version="3.0.81" />
 | 
			
		||||
		<PackageReference Include="NModbus.Serial" Version="3.0.81" />
 | 
			
		||||
		<PackageReference Include="S7netplus" Version="0.20.0" />
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<ProjectReference Include="..\ThingsGateway.Foundation.Variable\ThingsGateway.Foundation.Variable.csproj"/>
 | 
			
		||||
		<ProjectReference Include="..\ThingsGateway.Foundation.SourceGenerator\ThingsGateway.Foundation.SourceGenerator.csproj" PrivateAssets="all" OutputItemType="Analyzer" />
 | 
			
		||||
		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Variable\ThingsGateway.Foundation.Variable.csproj"/>
 | 
			
		||||
		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.SourceGenerator\ThingsGateway.Foundation.SourceGenerator.csproj" PrivateAssets="all" OutputItemType="Analyzer" />
 | 
			
		||||
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
@@ -837,10 +837,16 @@ public class OpcUaMaster : IAsyncDisposable
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var value in monitoreditem.DequeueValues())
 | 
			
		||||
                {
 | 
			
		||||
                    var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode)).GetAwaiter().GetResult();
 | 
			
		||||
 | 
			
		||||
                    var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode)).GetAwaiter().GetResult();
 | 
			
		||||
                    if (value.Value != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (value.Value.GetType().IsRichPrimitive())
 | 
			
		||||
                        {
 | 
			
		||||
                            DataChangedHandler?.Invoke((variableNode, monitoreditem, value, null));
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
 | 
			
		||||
                        if (data == null && value.Value != null)
 | 
			
		||||
                        {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,38 @@ namespace ThingsGateway.Foundation.OpcUa;
 | 
			
		||||
 | 
			
		||||
internal static class CollectionExtension
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 判断是否是元组类型
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="type">类型</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static bool IsValueTuple(this Type type)
 | 
			
		||||
    {
 | 
			
		||||
        return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 判断是否是富基元类型
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="type">类型</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static bool IsRichPrimitive(this Type? type)
 | 
			
		||||
    {
 | 
			
		||||
        if (type == null) return false;
 | 
			
		||||
 | 
			
		||||
        // 处理元组类型
 | 
			
		||||
        if (type.IsValueTuple()) return false;
 | 
			
		||||
 | 
			
		||||
        // 处理数组类型,基元数组类型也可以是基元类型
 | 
			
		||||
        if (type.IsArray) return type.GetElementType()?.IsRichPrimitive() ?? false;
 | 
			
		||||
 | 
			
		||||
        // 基元类型或值类型或字符串类型
 | 
			
		||||
        if (type.IsPrimitive || type.IsValueType || type == typeof(string)) return true;
 | 
			
		||||
 | 
			
		||||
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) return type.GenericTypeArguments[0].IsRichPrimitive();
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IEnumerable<List<T>> ChunkBetter<T>(this IEnumerable<T> source, int chunkSize)
 | 
			
		||||
    {
 | 
			
		||||
        if (source == null) throw new ArgumentNullException(nameof(source));
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,9 @@
 | 
			
		||||
			<PrivateAssets>all</PrivateAssets>
 | 
			
		||||
			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
			
		||||
		</PackageReference>
 | 
			
		||||
		<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
 | 
			
		||||
		<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
 | 
			
		||||
		<PackageReference Include="xunit" Version="2.9.3" />
 | 
			
		||||
		<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
 | 
			
		||||
		<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
 | 
			
		||||
			<PrivateAssets>all</PrivateAssets>
 | 
			
		||||
			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
			
		||||
		</PackageReference>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ using ThingsGateway.Common;
 | 
			
		||||
using ThingsGateway.DB;
 | 
			
		||||
using ThingsGateway.Debug;
 | 
			
		||||
using ThingsGateway.Foundation;
 | 
			
		||||
using ThingsGateway.Gateway.Application;
 | 
			
		||||
using ThingsGateway.NewLife;
 | 
			
		||||
using ThingsGateway.NewLife.Extension;
 | 
			
		||||
using ThingsGateway.NewLife.Threading;
 | 
			
		||||
@@ -103,7 +102,7 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                {
 | 
			
		||||
                    var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
 | 
			
		||||
                    var time = TimerX.Now - TimeSpan.FromDays(_driverPropertys.SaveDays);
 | 
			
		||||
 | 
			
		||||
                    string sql = $"""
 | 
			
		||||
                                        ALTER TABLE {_driverPropertys.NumberTableNameLow}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ public class SQLHistoryValue : IPrimaryIdEntity
 | 
			
		||||
    ///<summary>
 | 
			
		||||
    ///实时值
 | 
			
		||||
    ///</summary>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "实时值")]
 | 
			
		||||
    [SugarColumn(ColumnDescription = "实时值", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
 | 
			
		||||
    [AutoGenerateColumn(Order = 23, Visible = true, Sortable = true, Filterable = false)]
 | 
			
		||||
    public string Value { get; set; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ public class SQLRealValue : IPrimaryIdEntity
 | 
			
		||||
    ///<summary>
 | 
			
		||||
    ///实时值
 | 
			
		||||
    ///</summary>
 | 
			
		||||
    [SugarColumn(ColumnDescription = "实时值")]
 | 
			
		||||
    [SugarColumn(ColumnDescription = "实时值", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
 | 
			
		||||
    [AutoGenerateColumn(Order = 21, Visible = true, Sortable = true, Filterable = false)]
 | 
			
		||||
    public string Value { get; set; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -119,7 +119,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
 | 
			
		||||
                if (_driverPropertys.IsHistoryDB)
 | 
			
		||||
                {
 | 
			
		||||
                    {
 | 
			
		||||
                        var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
 | 
			
		||||
                        var time = TimerX.Now - TimeSpan.FromDays(_driverPropertys.SaveDays);
 | 
			
		||||
                        var tableNames = db.SplitHelper<SQLHistoryValue>().GetTables();//根据时间获取表名
 | 
			
		||||
                        var filtered = tableNames.Where(a => a.Date < time).ToList();
 | 
			
		||||
                        // 去掉最后一个
 | 
			
		||||
@@ -137,7 +137,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    {
 | 
			
		||||
                        var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
 | 
			
		||||
                        var time = TimerX.Now - TimeSpan.FromDays(_driverPropertys.SaveDays);
 | 
			
		||||
                        var tableNames = db.SplitHelper<SQLNumberHistoryValue>().GetTables();//根据时间获取表名
 | 
			
		||||
                        var filtered = tableNames.Where(a => a.Date < time).ToList();
 | 
			
		||||
                        // 去掉最后一个
 | 
			
		||||
 
 | 
			
		||||
@@ -93,6 +93,8 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (_driverPropertys.VariableAlarmEnable == false) return;
 | 
			
		||||
 | 
			
		||||
            using var db = BusinessDatabaseUtil.GetDb((DbType)_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
 | 
			
		||||
            if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
 | 
			
		||||
            {
 | 
			
		||||
@@ -103,7 +105,7 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                {
 | 
			
		||||
                    var time = TimerX.Now - TimeSpan.FromDays(-_driverPropertys.SaveDays);
 | 
			
		||||
                    var time = TimerX.Now - TimeSpan.FromDays(_driverPropertys.SaveDays);
 | 
			
		||||
 | 
			
		||||
                    await db.Deleteable<HistoryAlarm>().Where(a => a.EventTime < time).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,12 @@
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
 | 
			
		||||
#if NET10_0_OR_GREATER
 | 
			
		||||
 | 
			
		||||
using Microsoft.Extensions.Hosting;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
using MQTTnet.AspNetCore;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ using CSScripting;
 | 
			
		||||
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Microsoft.Extensions.Hosting;
 | 
			
		||||
 | 
			
		||||
using MQTTnet;
 | 
			
		||||
using MQTTnet.Internal;
 | 
			
		||||
@@ -43,7 +42,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll
 | 
			
		||||
 | 
			
		||||
#if NET10_0_OR_GREATER
 | 
			
		||||
 | 
			
		||||
    private IHost _webHost { get; set; }
 | 
			
		||||
    private Microsoft.Extensions.Hosting.IHost _webHost { get; set; }
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
    private IWebHost _webHost { get; set; }
 | 
			
		||||
 
 | 
			
		||||
@@ -206,7 +206,9 @@ public class OpcDaMaster : CollectBase
 | 
			
		||||
            if (TaskSchedulerLoop?.Stoped == true) return;
 | 
			
		||||
            if (DisposedValue)
 | 
			
		||||
                return;
 | 
			
		||||
            LogMessage?.Trace($"{ToString()} Change:{Environment.NewLine} {values?.ToSystemTextJsonString()}");
 | 
			
		||||
 | 
			
		||||
            if (LogMessage.LogLevel <= LogLevel.Trace)
 | 
			
		||||
                LogMessage?.Trace($"{ToString()} Change:{Environment.NewLine} {values?.ToSystemTextJsonString()}");
 | 
			
		||||
 | 
			
		||||
            foreach (var data in values)
 | 
			
		||||
            {
 | 
			
		||||
@@ -215,11 +217,13 @@ public class OpcDaMaster : CollectBase
 | 
			
		||||
                if (TaskSchedulerLoop?.Stoped == true) return;
 | 
			
		||||
                if (DisposedValue)
 | 
			
		||||
                    return;
 | 
			
		||||
                var type = data.Value.GetType();
 | 
			
		||||
                if (data.Value is Array)
 | 
			
		||||
                {
 | 
			
		||||
                    type = type.GetElementType();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //var type = data.Value.GetType();
 | 
			
		||||
                //if (data.Value is Array)
 | 
			
		||||
                //{
 | 
			
		||||
                //    type = type.GetElementType();
 | 
			
		||||
                //}
 | 
			
		||||
 | 
			
		||||
                if (!VariableAddresDicts.TryGetValue(data.Name, out var itemReads)) return;
 | 
			
		||||
 | 
			
		||||
                foreach (var item in itemReads)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@
 | 
			
		||||
			<PkgOPCFoundation_NetStandard_Opc_Ua_ClientPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Client)\lib\net8.0\*.*" />
 | 
			
		||||
			<PkgOPCFoundation_NetStandard_Opc_Ua_Client_ComplexTypesPackageFiles Include="$(PkgOPCFoundation_NetStandard_Opc_Ua_Client_ComplexTypes)\lib\net8.0\*.*" />
 | 
			
		||||
			<PkgSystem_Formats_Asn1PackageFiles Include="$(PkgSystem_Formats_Asn1)\lib\net8.0\*.*" />
 | 
			
		||||
			<PkgBitFaster_CachingPackageFiles Include="$(PkgBitFaster_Caching)\lib\net6.0\*.*" />
 | 
			
		||||
		</ItemGroup>
 | 
			
		||||
 | 
			
		||||
		<PropertyGroup>
 | 
			
		||||
@@ -23,6 +24,7 @@
 | 
			
		||||
		<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_ServerPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
		<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_Security_CertificatesPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
		<Copy SourceFiles="@(PkgSystem_Formats_Asn1PackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
		<Copy SourceFiles="@(PkgBitFaster_CachingPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
		<!--<Copy SourceFiles="@(PkgThingsGateway_Foundation_OpcUaPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />-->
 | 
			
		||||
		<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_ClientPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
		<Copy SourceFiles="@(PkgOPCFoundation_NetStandard_Opc_Ua_Client_ComplexTypesPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
 | 
			
		||||
@@ -36,6 +38,10 @@
 | 
			
		||||
				<Pack>true</Pack>
 | 
			
		||||
				<PackagePath>Content</PackagePath>
 | 
			
		||||
			</Content>
 | 
			
		||||
			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*BitFaster*.dll">
 | 
			
		||||
				<Pack>true</Pack>
 | 
			
		||||
				<PackagePath>Content</PackagePath>
 | 
			
		||||
			</Content>
 | 
			
		||||
		</ItemGroup>
 | 
			
		||||
	</Target>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -164,8 +164,10 @@ public class OpcUaMaster : CollectBase
 | 
			
		||||
                        //如果是订阅模式,连接时添加订阅组
 | 
			
		||||
                        if (_plc.OpcUaProperty?.ActiveSubscribe == true && CurrentDevice.VariableSourceReads.Count > 0 && _plc.Session.SubscriptionCount < CurrentDevice.VariableSourceReads.Count)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (cancellationToken.IsCancellationRequested) return;
 | 
			
		||||
                            foreach (var variableSourceRead in CurrentDevice.VariableSourceReads)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (cancellationToken.IsCancellationRequested) return;
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    if (_plc.Session.Subscriptions.FirstOrDefault(a => a.DisplayName == variableSourceRead.RegisterAddress) == null)
 | 
			
		||||
@@ -186,8 +188,8 @@ public class OpcUaMaster : CollectBase
 | 
			
		||||
                                    checkLog = false;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                LogMessage?.LogInformation("AddSubscriptions done");
 | 
			
		||||
                            }
 | 
			
		||||
                            LogMessage?.LogInformation("AddSubscriptions done");
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -329,6 +331,7 @@ public class OpcUaMaster : CollectBase
 | 
			
		||||
 | 
			
		||||
    private void DataChangedHandler((Opc.Ua.VariableNode variableNode, MonitoredItem monitoredItem, DataValue dataValue, JToken jToken) data)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        DateTime time = DateTime.Now;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -338,14 +341,15 @@ public class OpcUaMaster : CollectBase
 | 
			
		||||
                return;
 | 
			
		||||
            if (TaskSchedulerLoop?.Stoped == true) return;
 | 
			
		||||
 | 
			
		||||
            LogMessage?.Trace($"Change: {Environment.NewLine} {data.monitoredItem.StartNodeId} : {data.jToken?.ToString()}");
 | 
			
		||||
            if (LogMessage.LogLevel <= LogLevel.Trace)
 | 
			
		||||
                LogMessage?.Trace($"Change: {Environment.NewLine} {data.monitoredItem.StartNodeId} : {data.jToken?.ToString() ?? data.dataValue?.Value?.ToJsonString()}");
 | 
			
		||||
 | 
			
		||||
            //尝试固定点位的数据类型
 | 
			
		||||
            var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);
 | 
			
		||||
            //var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);
 | 
			
		||||
 | 
			
		||||
            if (!VariableAddresDicts.TryGetValue(data.monitoredItem.StartNodeId.ToString(), out var itemReads)) return;
 | 
			
		||||
 | 
			
		||||
            object value = data.jToken.GetObjectFromJToken();
 | 
			
		||||
            object value = data.jToken?.GetObjectFromJToken() ?? data.dataValue?.Value;
 | 
			
		||||
 | 
			
		||||
            var isGood = StatusCode.IsGood(data.dataValue.StatusCode);
 | 
			
		||||
            if (_driverProperties.SourceTimestampEnable)
 | 
			
		||||
@@ -382,6 +386,8 @@ public class OpcUaMaster : CollectBase
 | 
			
		||||
                LogMessage?.LogWarning(ex);
 | 
			
		||||
            success = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,7 @@ public partial class OpcUaMaster : IAsyncDisposable
 | 
			
		||||
        LogMessage?.AddLogger(logger);
 | 
			
		||||
 | 
			
		||||
        _plc.LogEvent = (a, b, c, d) => LogMessage?.Log((LogLevel)a, b, c, d);
 | 
			
		||||
        _plc.DataChangedHandler += (a) => LogMessage?.Trace($"id:{a.monitoredItem?.StartNodeId};stateCode:{a.dataValue?.StatusCode};value:{a.jToken?.ToString()}");
 | 
			
		||||
        _plc.DataChangedHandler += (a) => LogMessage?.Trace($"id:{a.monitoredItem?.StartNodeId};stateCode:{a.dataValue?.StatusCode};value:{a.jToken?.ToString() ?? a.dataValue?.Value?.ToJsonString()}");
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,10 @@
 | 
			
		||||
		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.377.21" GeneratePathProperty="true">
 | 
			
		||||
			<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
 | 
			
		||||
		</PackageReference>
 | 
			
		||||
		
 | 
			
		||||
		<PackageReference Include="BitFaster.Caching" Version="2.5.4" GeneratePathProperty="true">
 | 
			
		||||
			<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
 | 
			
		||||
		</PackageReference>
 | 
			
		||||
 | 
			
		||||
		<!--<PackageReference Include="System.Formats.Asn1" Version="8.0.2" GeneratePathProperty="true">
 | 
			
		||||
			<PrivateAssets>contentFiles;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,12 +21,12 @@
 | 
			
		||||
    <base href="/" />
 | 
			
		||||
    <title>ThingsGateway</title>
 | 
			
		||||
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"ThingsGateway.Server.styles.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css?v={this.GetType().Assembly.GetName().Version}") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"ThingsGateway.Server.styles.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css") />
 | 
			
		||||
    <link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css") />
 | 
			
		||||
 | 
			
		||||
    @* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@
 | 
			
		||||
    <!-- PWA Manifest -->
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@
 | 
			
		||||
                        <Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
 | 
			
		||||
                    }
 | 
			
		||||
                    @* 版本号 *@
 | 
			
		||||
                    <div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
 | 
			
		||||
                    <div class="px-1 navbar-header-text text-nowrap text-nowrap d-none d-lg-block">@_versionString</div>
 | 
			
		||||
 | 
			
		||||
                    @* 主题切换 *@
 | 
			
		||||
                    @* <ThemeToggle /> *@
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
Microsoft Visual Studio Solution File, Format Version 12.00
 | 
			
		||||
# Visual Studio Version 17
 | 
			
		||||
VisualStudioVersion = 17.9.34622.214
 | 
			
		||||
# Visual Studio Version 18
 | 
			
		||||
VisualStudioVersion = 18.0.11018.127 d18.0
 | 
			
		||||
MinimumVisualStudioVersion = 10.0.40219.1
 | 
			
		||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Server", "ThingsGateway.Server\ThingsGateway.Server.csproj", "{22875EFB-DADF-4612-A572-33BCC092F644}"
 | 
			
		||||
EndProject
 | 
			
		||||
@@ -111,12 +111,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "foundation", "foundation",
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "debug", "debug", "{053AB5FA-9742-96EC-76A1-2AEC739860C6}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Demo", "Foundation\ThingsGateway.Foundation.Demo\ThingsGateway.Foundation.Demo.csproj", "{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Benchmark", "Plugin\ThingsGateway.Foundation.Benchmark\ThingsGateway.Foundation.Benchmark.csproj", "{B0957BD6-CF77-36E7-B657-2D0DB85F386F}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.ScriptDebug", "ThingsGateway.ScriptDebug\ThingsGateway.ScriptDebug.csproj", "{F4AC662F-BE2C-6E1C-4BAF-370B968B3554}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{4F7AAAB1-C607-4FA0-9DFC-562940D76BC1}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmark", "benchmark", "{19611184-C452-4DF7-A0A3-CCFFCE1DD191}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Demo", "Plugin\ThingsGateway.Foundation.Demo\ThingsGateway.Foundation.Demo.csproj", "{C9B6F478-8CEC-1C71-00C2-19442F731E0E}"
 | 
			
		||||
EndProject
 | 
			
		||||
Global
 | 
			
		||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
			
		||||
		Debug|Any CPU = Debug|Any CPU
 | 
			
		||||
@@ -287,10 +291,6 @@ Global
 | 
			
		||||
		{EAEE6A03-D2E7-7283-0F7A-F15B6261EE96}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{EAEE6A03-D2E7-7283-0F7A-F15B6261EE96}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{EAEE6A03-D2E7-7283-0F7A-F15B6261EE96}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
@@ -299,6 +299,10 @@ Global
 | 
			
		||||
		{F4AC662F-BE2C-6E1C-4BAF-370B968B3554}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{F4AC662F-BE2C-6E1C-4BAF-370B968B3554}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{F4AC662F-BE2C-6E1C-4BAF-370B968B3554}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{C9B6F478-8CEC-1C71-00C2-19442F731E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{C9B6F478-8CEC-1C71-00C2-19442F731E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{C9B6F478-8CEC-1C71-00C2-19442F731E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{C9B6F478-8CEC-1C71-00C2-19442F731E0E}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(SolutionProperties) = preSolution
 | 
			
		||||
		HideSolutionNode = FALSE
 | 
			
		||||
@@ -345,12 +349,14 @@ Global
 | 
			
		||||
		{1D9CD7A3-9700-A851-0ABD-183347D9CC33} = {36510D70-161F-4241-B8D0-781E21032816}
 | 
			
		||||
		{E6EF2033-F02A-CDAD-5A72-EE397A89742E} = {36510D70-161F-4241-B8D0-781E21032816}
 | 
			
		||||
		{053AB5FA-9742-96EC-76A1-2AEC739860C6} = {36510D70-161F-4241-B8D0-781E21032816}
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA} = {2AC600BB-4325-4E0A-93A7-B1F53C8E2CA7}
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F} = {1D9CD7A3-9700-A851-0ABD-183347D9CC33}
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F} = {19611184-C452-4DF7-A0A3-CCFFCE1DD191}
 | 
			
		||||
		{4F7AAAB1-C607-4FA0-9DFC-562940D76BC1} = {36510D70-161F-4241-B8D0-781E21032816}
 | 
			
		||||
		{19611184-C452-4DF7-A0A3-CCFFCE1DD191} = {36510D70-161F-4241-B8D0-781E21032816}
 | 
			
		||||
		{C9B6F478-8CEC-1C71-00C2-19442F731E0E} = {4F7AAAB1-C607-4FA0-9DFC-562940D76BC1}
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(ExtensibilityGlobals) = postSolution
 | 
			
		||||
		RESX_Rules = {"EnabledRules":[]}
 | 
			
		||||
		RESX_NeutralResourcesLanguage = zh-Hans
 | 
			
		||||
		SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
 | 
			
		||||
		RESX_NeutralResourcesLanguage = zh-Hans
 | 
			
		||||
		RESX_Rules = {"EnabledRules":[]}
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
EndGlobal
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user