Compare commits

...

10 Commits

Author SHA1 Message Date
Diego
7e0b7aff2a feat: sqldb支持数组 2025-04-28 15:52:32 +08:00
Diego
6c450dcb09 feat: sqldb支持数组类型 2025-04-28 15:52:11 +08:00
Diego
227f44283f build: 10.5.8 2025-04-28 15:30:10 +08:00
Diego
74f6e79625 build: 10.5.7
优化opcua变量缓存
修复cron表达式间隔
2025-04-27 16:35:58 +08:00
Diego
cec43e2ce8 feat: 防呆设计,强制设置通道的最大并发数 2025-04-27 10:13:35 +08:00
Diego
7553b258bb build: 10.5.5 2025-04-26 17:46:31 +08:00
Diego
8bdbdc117e 支持bind链路设置多个通道 2025-04-26 17:10:46 +08:00
Diego
0e206be296 更新依赖 2025-04-26 16:17:02 +08:00
Diego
00b7353433 兼容性增强 2025-04-26 15:53:20 +08:00
Diego
44e7a83593 更新依赖 2025-04-26 15:02:57 +08:00
46 changed files with 240 additions and 130 deletions

View File

@@ -32,7 +32,7 @@ public class TimeTick
/// <summary>
/// 上次触发时间
/// </summary>
public DateTime LastTime { get; private set; } = DateTime.Now;
public DateTime LastTime { get; private set; } = DateTime.UtcNow;
/// <summary>
/// 是否触发时间刻度
@@ -62,7 +62,7 @@ public class TimeTick
return result;
}
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = true)
public DateTime GetNextTime(DateTime currentTime, bool setLastTime = false)
{
// 在没有 Cron 表达式的情况下,使用固定间隔
if (cron == null)
@@ -86,7 +86,7 @@ public class TimeTick
}
public DateTime GetNextTime(bool setLastTime = true) => GetNextTime(DateTime.UtcNow, setLastTime);
public DateTime GetNextTime(bool setLastTime = false) => GetNextTime(DateTime.UtcNow, setLastTime);
/// <summary>
/// 是否到达设置的时间间隔

View File

@@ -22,12 +22,34 @@ public static class JSRuntimeExtensions
/// 获取文化信息
/// </summary>
/// <param name="jsRuntime"></param>
public static ValueTask<string> GetCulture(this IJSRuntime jsRuntime) => jsRuntime.InvokeAsync<string>("getCultureLocalStorage");
public static async ValueTask<string> GetCulture(this IJSRuntime jsRuntime)
{
try
{
return await jsRuntime.InvokeAsync<string>("getCultureLocalStorage");
}
catch
{
return null;
}
}
/// <summary>
/// 设置文化信息
/// </summary>
/// <param name="jsRuntime"></param>
/// <param name="cultureName"></param>
public static ValueTask SetCulture(this IJSRuntime jsRuntime, string cultureName) => jsRuntime.InvokeVoidAsync("setCultureLocalStorage", cultureName);
public static async ValueTask SetCulture(this IJSRuntime jsRuntime, string cultureName)
{
try
{
await jsRuntime.InvokeVoidAsync("setCultureLocalStorage", cultureName);
}
catch
{
}
}
}

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.0.2" />
<PackageReference Include="BootstrapBlazor" Version="9.5.10" />
<PackageReference Include="BootstrapBlazor" Version="9.5.12" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<PluginVersion>10.5.3</PluginVersion>
<ProPluginVersion>10.5.3</ProPluginVersion>
<PluginVersion>10.5.9</PluginVersion>
<ProPluginVersion>10.5.9</ProPluginVersion>
<AuthenticationVersion>2.1.7</AuthenticationVersion>
</PropertyGroup>

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.9.5" />
<PackageReference Include="CS-Script" Version="4.9.6" />
</ItemGroup>
<ItemGroup>

View File

@@ -94,11 +94,11 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
return WaitLocks.GetOrAdd(key, (a) => new WaitLock(WaitLock.MaxCount));
}
public override Task StopAsync()
public override Task<Result> StopAsync(CancellationToken token)
{
WaitLocks.ForEach(a => a.Value.SafeDispose());
WaitLocks.Clear();
return base.StopAsync();
return base.StopAsync(token);
}
private ConcurrentDictionary<EndPoint, WaitLock> _waitLocks = new();

View File

@@ -128,17 +128,17 @@ public class OtherChannel : SetupConfigObject, IClientChannel
public Protocol Protocol => new Protocol("Other");
public DateTime LastReceivedTime { get; private set; }
public DateTimeOffset LastReceivedTime { get; private set; }
public DateTime LastSentTime { get; private set; }
public DateTimeOffset LastSentTime { get; private set; }
public bool IsClient => true;
public bool Online => true;
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return Task.CompletedTask;
return Task.FromResult(Result.Success);
}
public Task ConnectAsync(int millisecondsTimeout, CancellationToken token)

View File

@@ -80,7 +80,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
{
if (HeartbeatByte.SequenceEqual(e.ByteBlock.AsSegment(0, len)))
{
if (DateTime.UtcNow - socket.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}

View File

@@ -81,7 +81,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
try
{
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
if (DateTimeOffset.Now - tcpClient.LastSentTime < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}

View File

@@ -76,7 +76,7 @@ public static class PluginUtil
.SetOnClose(async (c, t) =>
{
await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
c.SafeClose($"{channelOptions.CheckClearTime}ms Timeout");
await c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout").ConfigureAwait(false);
});
};

View File

@@ -74,8 +74,9 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
public override async Task CloseAsync(string msg)
public override async Task<Result> CloseAsync(string msg, CancellationToken token)
{
if (Online)
{
try
@@ -83,11 +84,12 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//await _connectLock.WaitAsync().ConfigureAwait(false);
if (Online)
{
await base.CloseAsync(msg).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
}
return result;
}
}
finally
@@ -95,6 +97,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
//_connectLock.Release();
}
}
return Result.Success;
}
/// <inheritdoc/>

View File

@@ -72,7 +72,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
public override async Task CloseAsync(string msg)
public override async Task<Result> CloseAsync(string msg, CancellationToken token)
{
if (Online)
{
@@ -81,11 +81,12 @@ public class TcpClientChannel : TcpClient, IClientChannel
//await _connectLock.WaitAsync().ConfigureAwait(false);
if (Online)
{
await base.CloseAsync(msg).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
}
return result;
}
}
finally
@@ -93,6 +94,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
//_connectLock.Release();
}
}
return Result.Success;
}
/// <inheritdoc/>

View File

@@ -94,22 +94,22 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
}
/// <inheritdoc/>
public override async Task StopAsync()
public override async Task<Result> StopAsync(CancellationToken token)
{
if (Monitors.Any())
{
try
{
await _connectLock.WaitAsync().ConfigureAwait(false);
await _connectLock.WaitAsync(token).ConfigureAwait(false);
if (Monitors.Any())
{
await ClearAsync().ConfigureAwait(false);
var iPHost = Monitors.FirstOrDefault()?.Option.IpHost;
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
if (!Monitors.Any())
Logger?.Info($"{iPHost}{DefaultResource.Localizer["ServiceStoped"]}");
return result;
}
}
finally
@@ -120,8 +120,10 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
return Result.Success; ;
}
@@ -192,9 +194,9 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
public ChannelEventHandler Stoping { get; set; } = new();
/// <inheritdoc/>
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return StopAsync();
return StopAsync(token);
}
/// <inheritdoc/>

View File

@@ -64,10 +64,10 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public override Task CloseAsync(string msg)
public override Task<Result> CloseAsync(string msg, CancellationToken token)
{
WaitHandlePool.SafeDispose();
return base.CloseAsync(msg);
return base.CloseAsync(msg, token);
}
/// <inheritdoc/>

View File

@@ -73,9 +73,9 @@ public class UdpSessionChannel : UdpSession, IClientChannel
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
/// <inheritdoc/>
public Task CloseAsync(string msg)
public Task<Result> CloseAsync(string msg, CancellationToken token)
{
return StopAsync();
return StopAsync(token);
}
/// <inheritdoc/>
@@ -127,26 +127,28 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
/// <inheritdoc/>
public override async Task StopAsync()
public override async Task<Result> StopAsync(CancellationToken token)
{
if (Monitor != null)
{
try
{
await _connectLock.WaitAsync().ConfigureAwait(false);
await _connectLock.WaitAsync(token).ConfigureAwait(false);
if (Monitor != null)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
if (Monitor == null)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
Logger?.Info($"{DefaultResource.Localizer["ServiceStoped"]}");
}
return result;
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
}
finally
@@ -156,7 +158,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
else
{
await base.StopAsync().ConfigureAwait(false);
var result = await base.StopAsync(token).ConfigureAwait(false);
return result;
}
}

View File

@@ -46,9 +46,9 @@ public abstract class DeviceBase : DisposableObject, IDevice
return;
if (channel.Collects.Count > 0)
{
var device = channel.Collects.First();
if (device.GetType() != GetType())
throw new InvalidOperationException("The channel already exists in the device of another type");
//var device = channel.Collects.First();
//if (device.GetType() != GetType())
// throw new InvalidOperationException("The channel already exists in the device of another type");
if (!SupportMultipleDevice())
throw new InvalidOperationException("The proactive response device does not support multiple devices");
@@ -97,9 +97,17 @@ public abstract class DeviceBase : DisposableObject, IDevice
Channel.Stoping.Add(ChannelStoping);
Channel.Started.Add(ChannelStarted);
Channel.ChannelReceived.Add(ChannelReceived);
SetChannel();
}
}
protected virtual void SetChannel()
{
Channel.ChannelOptions.MaxConcurrentCount = 1;
}
/// <inheritdoc/>
~DeviceBase()
{
@@ -221,6 +229,14 @@ public abstract class DeviceBase : DisposableObject, IDevice
}
else
{
if (Channel?.Collects?.Count > 1)
{
var dataHandlingAdapter = GetDataAdapter();
if (adapter.GetType() != dataHandlingAdapter.GetType())
{
clientChannel.SetDataHandlingAdapter(dataHandlingAdapter);
}
}
}
}
@@ -944,7 +960,7 @@ public abstract class DeviceBase : DisposableObject, IDevice
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
{
client.WaitHandlePool?.SafeDispose();
client.SafeClose();
client.Close();
}
}

View File

@@ -10,8 +10,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.4" />
<PackageReference Include="TouchSocket" Version="3.0.26" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.0.26" />
<PackageReference Include="TouchSocket" Version="3.1.0" />
<PackageReference Include="TouchSocket.SerialPorts" Version="3.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -87,12 +87,12 @@ public abstract class BusinessBaseWithCacheIntervalAlarmModel<VarModel, DevModel
// 触发一次设备状态变化和变量值变化事件
CollectDevices?.ForEach(a =>
{
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
});
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}

View File

@@ -85,12 +85,12 @@ public abstract class BusinessBaseWithCacheIntervalDeviceModel<VarModel, DevMode
CollectDevices?.ForEach(a =>
{
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine)
if (a.Value.DeviceStatus == DeviceStatusEnum.OnLine && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
DeviceStatusChange(a.Value, a.Value.Adapt<DeviceBasicData>());
});
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}

View File

@@ -73,7 +73,7 @@ public abstract class BusinessBaseWithCacheIntervalVariableModel<T> : BusinessBa
// 触发一次变量值变化事件
IdVariableRuntimes.ForEach(a =>
{
if (a.Value.IsOnline)
if (a.Value.IsOnline && _businessPropertyWithCacheInterval.BusinessUpdateEnum != BusinessUpdateEnum.Interval)
VariableValueChange(a.Value, a.Value.Adapt<VariableBasicData>());
});
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Text;
namespace ThingsGateway.Gateway.Application;
public struct TopicArray

View File

@@ -140,7 +140,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 缓存超时
/// </summary>
[SugarColumn(ColumnDescription = "缓存超时")]
[SugarColumn(ColumnDescription = "缓存超时", IsNullable = true, DefaultValue = "500")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(100)]
public override int CacheTimeout { get; set; } = 500;
@@ -148,7 +148,7 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 连接超时
/// </summary>
[SugarColumn(ColumnDescription = "连接超时")]
[SugarColumn(ColumnDescription = "连接超时", IsNullable = true, DefaultValue = "3000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(100)]
public override ushort ConnectTimeout { get; set; } = 3000;
@@ -156,33 +156,36 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// <summary>
/// 最大并发数
/// </summary>
[SugarColumn(ColumnDescription = "最大并发数")]
[SugarColumn(ColumnDescription = "最大并发数", IsNullable = true, DefaultValue = "1")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(1)]
public override int MaxConcurrentCount { get; set; } = 1;
[SugarColumn(ColumnDescription = "最大连接数")]
[SugarColumn(ColumnDescription = "最大连接数", IsNullable = true, DefaultValue = "10000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int MaxClientCount { get; set; } = 10000;
[SugarColumn(ColumnDescription = "客户端滑动过期时间")]
[SugarColumn(ColumnDescription = "客户端滑动过期时间", IsNullable = true, DefaultValue = "120000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int CheckClearTime { get; set; } = 120000;
[SugarColumn(ColumnDescription = "心跳内容", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override string Heartbeat { get; set; } = "Heartbeat";
#region dtu终端
[SugarColumn(ColumnDescription = "心跳间隔")]
[SugarColumn(ColumnDescription = "心跳间隔", IsNullable = true, DefaultValue = "60000")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override int HeartbeatTime { get; set; } = 60000;
[SugarColumn(ColumnDescription = "DtuId", IsNullable = true)]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override string DtuId { get; set; }
#endregion
[SugarColumn(ColumnDescription = "Dtu类型")]
[SugarColumn(ColumnDescription = "Dtu类型", IsNullable = true, DefaultValue = "0")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
public override DtuSeviceType DtuSeviceType { get; set; }

View File

@@ -131,7 +131,7 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
public void Dispose()
{
Config?.SafeDispose();
//Config?.SafeDispose();
GlobalData.Channels.TryRemove(Id, out _);
DeviceThreadManage = null;
@@ -146,4 +146,57 @@ public class ChannelRuntime : Channel, IChannelOptions, IDisposable
return $"{Name}[{base.ToString()}]";
}
public IChannel GetChannel(TouchSocketConfig config)
{
lock (GlobalData.Channels)
{
if (DeviceThreadManage?.Channel?.DisposedValue == false)
return DeviceThreadManage?.Channel;
if (ChannelType == ChannelTypeEnum.TcpService
|| ChannelType == ChannelTypeEnum.SerialPort
|| ChannelType == ChannelTypeEnum.UdpSession
)
{
//获取相同配置的Tcp服务或Udp服务或COM
var same = GlobalData.Channels.FirstOrDefault(a =>
{
if (a.Value == this)
return false;
if (a.Value.DeviceThreadManage?.Channel?.DisposedValue == true || a.Value.DeviceThreadManage?.Channel?.DisposedValue == null)
return false;
if (a.Value.ChannelType == ChannelType)
{
if (a.Value.ChannelType == ChannelTypeEnum.TcpService)
if (a.Value.BindUrl == BindUrl)
return true;
if (a.Value.ChannelType == ChannelTypeEnum.UdpSession)
if ((!BindUrl.IsNullOrWhiteSpace()) && a.Value.BindUrl == BindUrl)
return true;
if (a.Value.ChannelType == ChannelTypeEnum.SerialPort)
if (a.Value.PortName == PortName)
return true;
}
return false;
}).Value;
if (same != null)
{
return same.GetChannel(config);
}
}
if (DeviceThreadManage?.Channel?.DisposedValue == false)
return DeviceThreadManage?.Channel;
var ichannel = config.GetChannel(this);
return ichannel;
}
}
}

View File

@@ -26,7 +26,7 @@ namespace ThingsGateway.Gateway.Application;
public class DeviceRuntime : Device, IDisposable
{
protected volatile DeviceStatusEnum _deviceStatus = DeviceStatusEnum.Default;
private string? _lastErrorMessage;
/// <summary>

View File

@@ -19,5 +19,8 @@ public class VariableMapper : IRegister
{
config.ForType<Variable, VariableRuntime>()
.Map(dest => dest.Value, src => src.InitValue);
config.ForType<VariableRuntime, VariableRuntime>()
.Ignore(dest => dest.DeviceRuntime);
}
}

View File

@@ -16,8 +16,6 @@ using System.Collections.Concurrent;
using ThingsGateway.NewLife;
using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application;
internal sealed class ChannelThreadManage : IChannelThreadManage
@@ -115,8 +113,6 @@ internal sealed class ChannelThreadManage : IChannelThreadManage
{
await PrivateRemoveChannelsAsync(channelRuntimes.Select(a => a.Id)).ConfigureAwait(false);
BytePool.Default.Clear();
await channelRuntimes.ParallelForEachAsync(async (channelRuntime, token) =>
{
try

View File

@@ -117,10 +117,9 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
// 添加默认日志记录器
LogMessage.AddLogger(new EasyLogger(logger.Log_Out) { LogLevel = TouchSocket.Core.LogLevel.Trace });
var ichannel = config.GetChannel(channelRuntime);
// 根据配置获取通道实例
Channel = ichannel;
Channel = channelRuntime.GetChannel(config);
//初始设置输出文本日志
SetLog(CurrentChannel.LogLevel);
@@ -134,7 +133,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
CancellationToken = CancellationTokenSource.Token;
CancellationToken.Register(() => GlobalData.DeviceStatusChangeEvent -= GlobalData_DeviceStatusChangeEvent);
_ = Task.Run(() => CheckThreadAsync(CancellationToken));
_ = Task.Run(() => CheckRedundantAsync(CancellationToken));
}
@@ -901,7 +900,8 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
await NewDeviceLock.WaitAsync().ConfigureAwait(false);
await PrivateRemoveDevicesAsync(Drivers.Keys).ConfigureAwait(false);
Channel?.SafeDispose();
if (Channel?.Collects.Count == 0)
Channel?.SafeDispose();
LogMessage?.LogInformation(Localizer["ChannelDispose", CurrentChannel?.Name ?? string.Empty]);

View File

@@ -76,6 +76,7 @@ public class Startup : AppStartup
if (configId.Count() > 1) throw new($"Sqlsugar connect configId: {configId.Key} Duplicate!");
}
//遍历配置
DbContext.DbConfigs?.ForEach(it =>
{
@@ -84,6 +85,20 @@ public class Startup : AppStartup
if (it.InitTable == true)
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
});
//兼容变量名称唯一键处理
try
{
using var db = DbContext.GetDB<Variable>();
if (db.DbMaintenance.IsAnyIndex("unique_variable_name"))
{
var tables = db.DbMaintenance.DropIndex("unique_variable_name");
}
}
catch { }
var fullName = Assembly.GetExecutingAssembly().FullName;//获取程序集全名
CodeFirstUtils.CodeFirst(fullName!);//CodeFirst
@@ -109,6 +124,9 @@ public class Startup : AppStartup
}
catch { }
serviceProvider.GetService<IHostApplicationLifetime>().ApplicationStarted.Register(() =>
{
serviceProvider.GetService<ILoggerFactory>().CreateLogger(nameof(ThingsGateway)).LogInformation("ThingsGateway is started...");

View File

@@ -8,8 +8,8 @@
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="Rougamo.Fody" Version="5.0.0" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.26" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.0.26" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.0" />
<PackageReference Include="TouchSocket.WebApi.Swagger" Version="3.1.0" />
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
</ItemGroup>

View File

@@ -105,8 +105,8 @@ public class TcpSessionClientDto
public string PluginInfos { get; set; }
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public DateTime LastReceivedTime { get; set; }
public DateTimeOffset LastReceivedTime { get; set; }
[AutoGenerateColumn(Searchable = true, Filterable = true, Sortable = true)]
public DateTime LastSentTime { get; set; }
public DateTimeOffset LastSentTime { get; set; }
}

View File

@@ -1299,7 +1299,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
try
{
if (Disposed) return;
await Task.Delay(2000);
await Task.Delay(1000);
await OnClickSearch(SearchText);
Value = GetValue(Value);

View File

@@ -212,8 +212,8 @@ public partial class VariableEditComponent
{
var component = new BootstrapDynamicComponent(data.VariablePropertyUIType, new Dictionary<string, object?>
{
[nameof(VariableEditComponent.Model)] = Model,
[nameof(DeviceEditComponent.PluginPropertyEditorItems)] = data.EditorItems,
[nameof(IPropertyUIBase.Model)] = Model,
[nameof(IPropertyUIBase.PluginPropertyEditorItems)] = data.EditorItems,
});
VariablePropertyRenderFragments.AddOrUpdate(id, component.Render());
}

View File

@@ -15,7 +15,7 @@ using TouchSocket.Rpc;
namespace ThingsGateway.Management;
public partial class ReverseCallbackServer : RpcServer
public partial class ReverseCallbackServer : SingletonRpcServer
{
[DmtpRpc(MethodInvoke = true)]
public void UpdateGatewayData(List<DeviceDataWithValue> deviceDatas)

View File

@@ -13,7 +13,7 @@ using TouchSocket.Rpc;
namespace ThingsGateway.Management;
public partial class UpgradeRpcServer : RpcServer
public partial class UpgradeRpcServer : SingletonRpcServer
{
[DmtpRpc(MethodInvoke = true)]
public void Restart()

View File

@@ -19,7 +19,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
{
Func = func;
FuncDict.Add(this, func);
if (!DeviceChangedTriggerNodeDict.TryGetValue(Text, out var list))
if (!DeviceChangedTriggerNodeDict.TryGetValue(Text ?? string.Empty, out var list))
{
var deviceChangedTriggerNodes = new ConcurrentList<DeviceChangedTriggerNode>();
deviceChangedTriggerNodes.Add(this);
@@ -44,7 +44,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
private static void GlobalData_DeviceStatusChangeEvent(DeviceRuntime deviceRunTime, DeviceBasicData deviceData)
{
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceData.Name, out var deviceChangedTriggerNodes) && deviceChangedTriggerNodes?.Count > 0)
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceData.Name ?? string.Empty, out var deviceChangedTriggerNodes) && deviceChangedTriggerNodes?.Count > 0)
{
if (!DeviceDatas.IsAddingCompleted)
{
@@ -63,7 +63,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
return DeviceDatas.GetConsumingEnumerable().ParallelForEachAsync((async (deviceDatas, token) =>
{
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceDatas.Name, out var valueChangedTriggerNodes))
if (DeviceChangedTriggerNodeDict.TryGetValue(deviceDatas.Name ?? string.Empty, out var valueChangedTriggerNodes))
{
await valueChangedTriggerNodes.ParallelForEachAsync(async (item, token) =>
{
@@ -89,7 +89,7 @@ public class DeviceChangedTriggerNode : TextNode, ITriggerNode, IDisposable
public void Dispose()
{
FuncDict.Remove(this);
if (DeviceChangedTriggerNodeDict.TryGetValue(Text, out var list))
if (DeviceChangedTriggerNodeDict.TryGetValue(Text ?? string.Empty, out var list))
{
list.Remove(this);
}

View File

@@ -251,7 +251,7 @@ public class OpcUaMaster : IDisposable
DisplayName = subscriptionName
};
List<MonitoredItem> monitoredItems = new();
var variableNodes = loadType ? await ReadNodesAsync(items, cancellationToken).ConfigureAwait(false) : null;
var variableNodes = loadType ? await ReadNodesAsync(items, false, cancellationToken).ConfigureAwait(false) : null;
for (int i = 0; i < items.Length; i++)
{
try
@@ -743,7 +743,7 @@ public class OpcUaMaster : IDisposable
NodeId = new NodeId(item.Key),
AttributeId = Attributes.Value,
};
var variableNode = await ReadNodeAsync(item.Key, false, cancellationToken).ConfigureAwait(false);
var variableNode = await ReadNodeAsync(item.Key, false, false, cancellationToken).ConfigureAwait(false);
var dataValue = JsonUtils.Decode(
m_session.MessageContext,
variableNode.DataType,
@@ -793,9 +793,10 @@ public class OpcUaMaster : IDisposable
{
if (m_session != null)
{
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false);
foreach (var value in monitoreditem.DequeueValues())
{
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode));
if (value.Value != null)
{
var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
@@ -974,7 +975,7 @@ public class OpcUaMaster : IDisposable
List<(string, DataValue, JToken)> jTokens = new();
for (int i = 0; i < results.Count; i++)
{
var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, cancellationToken).ConfigureAwait(false);
var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, StatusCode.IsGood(results[i].StatusCode), cancellationToken).ConfigureAwait(false);
var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable);
var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value);
jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken));
@@ -985,7 +986,7 @@ public class OpcUaMaster : IDisposable
/// <summary>
/// 从服务器或缓存读取节点
/// </summary>
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true)
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true, bool cache = true)
{
if (!isOnlyServer)
{
@@ -1025,14 +1026,15 @@ public class OpcUaMaster : IDisposable
VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault();
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
if (cache)
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
return variableNode;
}
/// <summary>
/// 从服务器或缓存读取节点
/// </summary>
private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, CancellationToken cancellationToken = default)
private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, bool cache = true, CancellationToken cancellationToken = default)
{
if (!isOnlyServer)
{
@@ -1073,7 +1075,9 @@ public class OpcUaMaster : IDisposable
if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject)
await typeSystem.LoadType(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false);
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
if (cache)
_variableDicts.AddOrUpdate(nodeIdStr, a => variableNode, (a, b) => variableNode);
return variableNode;
}
@@ -1127,7 +1131,7 @@ public class OpcUaMaster : IDisposable
/// <summary>
/// 从服务器读取节点
/// </summary>
private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, CancellationToken cancellationToken = default)
private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, bool cache = false, CancellationToken cancellationToken = default)
{
List<Node> result = new(nodeIdStrs.Length);
foreach (var items in nodeIdStrs.ChunkBetter(OpcUaProperty.GroupSize))
@@ -1171,7 +1175,8 @@ public class OpcUaMaster : IDisposable
}
else
{
_variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node);
if (cache)
_variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node);
if (node.DataType != NodeId.Null && TypeInfo.GetBuiltInType(node.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject)
{
await typeSystem.LoadType(node.DataType, ct: cancellationToken).ConfigureAwait(false);

View File

@@ -12,6 +12,8 @@ using BootstrapBlazor.Components;
using Mapster;
using Newtonsoft.Json.Linq;
using SqlSugar;
using ThingsGateway.Admin.Application;
@@ -66,7 +68,7 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariableMode
_config.ForType<VariableRuntime, QuestDBHistoryValue>()
//.Map(dest => dest.Id, src => CommonUtils.GetSingleId())
.Map(dest => dest.Id, src => src.Id)//Id更改为变量Id
.Map(dest => dest.Value, src => src.Value == null ? string.Empty : src.Value.ToString() ?? string.Empty)
.Map(dest => dest.Value, src => src.Value != null ? src.Value.GetType() == typeof(string) ? src.Value.ToString() : JToken.FromObject(src.Value).ToString() : string.Empty)
.Map(dest => dest.CollectTime, (src) => src.CollectTime < DateTime.MinValue ? utcTime : src.CollectTime!.Value.ToUniversalTime())//注意sqlsugar插入时无时区直接utc时间
.Map(dest => dest.CreateTime, (src) => DateTime.UtcNow)
;//注意sqlsugar插入时无时区直接utc时间

View File

@@ -12,6 +12,8 @@ using BootstrapBlazor.Components;
using Mapster;
using Newtonsoft.Json.Linq;
using SqlSugar;
using ThingsGateway.Admin.Application;
@@ -154,11 +156,17 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
{
_config = new TypeAdapterConfig();
_config.ForType<VariableRuntime, SQLHistoryValue>()
//.Map(dest => dest.Id, (src) =>CommonUtils.GetSingleId())
.Map(dest => dest.Id, src => src.Id)//Id更改为变量Id
.Map(dest => dest.Value, src => src.Value == null ? string.Empty : src.Value.ToString() ?? string.Empty)
.Map(dest => dest.Id, (src) => CommonUtils.GetSingleId())
//.Map(dest => dest.Id, src => src.Id)//Id更改为变量Id
.Map(dest => dest.Value, src => src.Value != null ? src.Value.GetType() == typeof(string) ? src.Value.ToString() : JToken.FromObject(src.Value).ToString() : string.Empty)
.Map(dest => dest.CreateTime, (src) => DateTime.Now);
_config.ForType<VariableRuntime, SQLRealValue>()
//.Map(dest => dest.Id, (src) =>CommonUtils.GetSingleId())
.Map(dest => dest.Id, src => src.Id)//Id更改为变量Id
.Map(dest => dest.Value, src => src.Value != null ? src.Value.GetType() == typeof(string) ? src.Value.ToString() : JToken.FromObject(src.Value).ToString() : string.Empty);
_exRealTimerTick = new(_driverPropertys.RealTableBusinessInterval);
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
@@ -220,7 +228,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
var groups = varList.GroupBy(a => a.Group);
foreach (var item in groups)
{
var result = await UpdateAsync(item.Adapt<List<SQLRealValue>>(), cancellationToken).ConfigureAwait(false);
var result = await UpdateAsync(item.Adapt<List<SQLRealValue>>(_config), cancellationToken).ConfigureAwait(false);
if (success != result.IsSuccess)
{
if (!result.IsSuccess)
@@ -231,7 +239,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariableModel<
}
else
{
var result = await UpdateAsync(varList.Adapt<List<SQLRealValue>>(), cancellationToken).ConfigureAwait(false);
var result = await UpdateAsync(varList.Adapt<List<SQLRealValue>>(_config), cancellationToken).ConfigureAwait(false);
if (success != result.IsSuccess)
{
if (!result.IsSuccess)

View File

@@ -1,32 +1,11 @@
<Project>
<Target Name="CopyNugetPackages" AfterTargets="Build">
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<!-- setting up the variable for convenience -->
<ApplicationPackageFiles Include="$(PkgMQTTnet_AspNetCore)\lib\net6.0\*.*" />
<MQTTnetApplicationPackageFiles Include="$(PkgMQTTnet)\lib\net6.0\*.*" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0'">
<!-- setting up the variable for convenience -->
<ApplicationPackageFiles Include="$(PkgMQTTnet_AspNetCore)\lib\net8.0\*.*" />
<MQTTnet_ServerApplicationPackageFiles Include="$(PkgMQTTnet_Server)\lib\net8.0\*.*" />
<MQTTnetApplicationPackageFiles Include="$(PkgMQTTnet)\lib\net8.0\*.*" />
</ItemGroup>
<PropertyGroup>
<ApplicationFolder>$(TargetDir)</ApplicationFolder>
</PropertyGroup>
<Copy SourceFiles="@(ApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(MQTTnet_ServerApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
<Copy SourceFiles="@(MQTTnetApplicationPackageFiles)" DestinationFolder="$(ApplicationFolder)%(RecursiveDir)" />
</Target>
<!--在构建后触发的。它通过在 Nuget 包的 Content 文件夹中包含目标目录中的所有文件和子文件夹来创建 nuget 包-->
<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build">
<ItemGroup>
<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*MQTT*.dll">
<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Http*.dll">
<Pack>true</Pack>
<PackagePath>Content</PackagePath>
</Content>

View File

@@ -12,8 +12,6 @@ using BootstrapBlazor.Components;
using Mapster;
using System.Text;
using ThingsGateway.Foundation;
using ThingsGateway.NewLife.Extension;

View File

@@ -12,8 +12,6 @@ using Mapster;
using RabbitMQ.Client;
using System.Text;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Extension.Generic;

View File

@@ -3,6 +3,7 @@
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹自动合并该文件夹里面所有json文件
"IgnoreConfigurationFiles": [ "" ],
"ExternalAssemblies": [ "" ]
"ExternalAssemblies": [ "" ],
"DetailedErrors": true
}

View File

@@ -162,7 +162,7 @@ public class FileHostService : BackgroundService, IFileHostService
_log.Exception(ex);
try
{
await TcpDmtpService.StopAsync().ConfigureAwait(false);
await TcpDmtpService.StopAsync(default).ConfigureAwait(false);
}
catch
{

View File

@@ -4,7 +4,7 @@ using TouchSocket.Rpc;
namespace ThingsGateway.Upgrade;
public partial class FileRpcServer : RpcServer
public partial class FileRpcServer : SingletonRpcServer
{
private readonly ILog _logger;

View File

@@ -16,7 +16,7 @@
<ItemGroup>
<PackageReference Include="TouchSocket.Dmtp" Version="3.0.26" />
<PackageReference Include="TouchSocket.Dmtp" Version="3.1.0" />
</ItemGroup>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>10.5.3</Version>
<Version>10.5.9</Version>
</PropertyGroup>
<ItemGroup>