mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-21 19:14:30 +08:00
Compare commits
26 Commits
10.10.20.0
...
10.11.22.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fc09a52da1 | ||
![]() |
5436b91c89 | ||
![]() |
d1c46f51a6 | ||
![]() |
4539d8d198 | ||
![]() |
9ea9529a5f | ||
![]() |
4e6be23aac | ||
![]() |
2fabbd236b | ||
![]() |
163a66530e | ||
![]() |
29073a00c4 | ||
![]() |
c6d4d1ecfa | ||
![]() |
ba16889cad | ||
![]() |
5aaed35b0f | ||
![]() |
df067c91eb | ||
![]() |
2078b4a60b | ||
![]() |
20a2e3ff8e | ||
![]() |
61a973b1b5 | ||
![]() |
cbd72e2081 | ||
![]() |
4e0377b20c | ||
![]() |
fd318d3cdc | ||
![]() |
515bdb9700 | ||
![]() |
46c1780017 | ||
![]() |
fe78a4c3ca | ||
![]() |
2d7effadf9 | ||
![]() |
346c560f8b | ||
![]() |
8e3bd89f61 | ||
![]() |
6da142d080 |
@@ -37,9 +37,8 @@ public class FileController : ControllerBase
|
||||
var root = Directory.GetCurrentDirectory();
|
||||
var wwwroot = Path.Combine(root, "wwwroot");
|
||||
var filePath = Path.Combine(wwwroot, fileName);
|
||||
// 防止路径穿越攻击
|
||||
#pragma warning disable CA3003
|
||||
if (filePath.Contains("..") || !System.IO.File.Exists(filePath))
|
||||
if ((!(fileName.StartsWith(@"../Logs") || fileName.StartsWith(@"..\Logs")) && filePath.Contains("..")) || !System.IO.File.Exists(filePath))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
@@ -377,9 +377,9 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
/// 获取用户拥有的资源
|
||||
/// </summary>
|
||||
/// <param name="id">用户id</param>
|
||||
public async Task<GrantResourceData> OwnResourceAsync(long id)
|
||||
public Task<GrantResourceData> OwnResourceAsync(long id)
|
||||
{
|
||||
return await _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource).ConfigureAwait(false);
|
||||
return _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -505,10 +505,10 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
|
||||
var password = await GetDefaultPassWord(true).ConfigureAwait(false);//获取默认密码,这里不走Aop所以需要加密一下
|
||||
using var db = GetDB();
|
||||
//重置密码
|
||||
if (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
|
||||
if ((await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
|
||||
{
|
||||
Password = password
|
||||
}, it => it.Id == id).ConfigureAwait(false))
|
||||
}, it => it.Id == id).ConfigureAwait(false)) > 0)
|
||||
{
|
||||
DeleteUserFromCache(id);//从cache删除用户信息
|
||||
var verificatInfoIds = _verificatInfoService.GetListByUserId(id);
|
||||
|
@@ -185,12 +185,12 @@ internal sealed class UserCenterService : BaseService<SysUser>, IUserCenterServi
|
||||
using var db = GetDB();
|
||||
|
||||
//更新指定字段
|
||||
var result = await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
|
||||
var result = (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
|
||||
{
|
||||
Email = input.Email,
|
||||
Phone = input.Phone,
|
||||
Avatar = input.Avatar,
|
||||
}, it => it.Id == UserManager.UserId).ConfigureAwait(false);
|
||||
}, it => it.Id == UserManager.UserId).ConfigureAwait(false)) > 0;
|
||||
if (result)
|
||||
_userService.DeleteUserFromCache(UserManager.UserId);//cache删除用户数据
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@
|
||||
<Target Name="AdminPostPublish" AfterTargets="Publish">
|
||||
<ItemGroup>
|
||||
<!-- setting up the variable for convenience -->
|
||||
<AdminFiles Include="bin\$(Configuration)\$(TargetFramework)\SeedData\**" />
|
||||
<AdminFiles Include="$(OutputPath)\$(TargetFramework)\SeedData\**" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
</PropertyGroup>
|
||||
|
@@ -15,6 +15,7 @@ using System.Text;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.DB;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Log;
|
||||
|
||||
namespace ThingsGateway.AdminServer;
|
||||
@@ -64,7 +65,7 @@ public class Program
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
builder.Host.UseSystemd();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (Runtime.IsLegacyWindows)
|
||||
builder.Logging.ClearProviders(); //去除默认的事件日志提供者,某些情况下会日志输出异常,导致程序崩溃
|
||||
}).ConfigureBuilder(builder =>
|
||||
{
|
||||
|
@@ -183,19 +183,22 @@ public class Startup : AppStartup
|
||||
services.AddScoped<IAuthorizationHandler, BlazorServerAuthenticationHandler>();
|
||||
services.AddScoped<AuthenticationStateProvider, BlazorServerAuthenticationStateProvider>();
|
||||
|
||||
if (!NewLife.Runtime.IsLegacyWindows)
|
||||
{
|
||||
#if NET9_0_OR_GREATER
|
||||
var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
var certificate = X509CertificateLoader.LoadPkcs12FromFile("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
#else
|
||||
var certificate = new X509Certificate2("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
var certificate = new X509Certificate2("ThingsGateway.pfx", "ThingsGateway", X509KeyStorageFlags.EphemeralKeySet);
|
||||
#endif
|
||||
services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo("Keys"))
|
||||
.ProtectKeysWithCertificate(certificate)
|
||||
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
|
||||
{
|
||||
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
|
||||
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
|
||||
});
|
||||
services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo("Keys"))
|
||||
.ProtectKeysWithCertificate(certificate)
|
||||
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
|
||||
{
|
||||
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
|
||||
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Use(IApplicationBuilder applicationBuilder, IWebHostEnvironment env)
|
||||
|
@@ -30,9 +30,27 @@ public class ImportPreviewOutputBase
|
||||
/// <summary>
|
||||
/// 返回状态
|
||||
/// </summary>
|
||||
public ConcurrentList<(int Row, bool Success, string? ErrorMessage)> Results { get; set; } = new();
|
||||
public ConcurrentList<ImportPreviewResult> Results { get; set; } = new();
|
||||
}
|
||||
public class ImportPreviewResult
|
||||
{
|
||||
public ImportPreviewResult()
|
||||
{
|
||||
|
||||
}
|
||||
public ImportPreviewResult(int row, bool success, string error)
|
||||
{
|
||||
this.Row = row;
|
||||
this.Success = success;
|
||||
this.ErrorMessage = error;
|
||||
}
|
||||
|
||||
public int Row { get; set; }
|
||||
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 导入预览
|
||||
/// </summary>
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.9.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -9,6 +9,7 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace ThingsGateway.DB;
|
||||
|
||||
@@ -41,4 +42,31 @@ public static class FileExtensions
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
/// <summary>
|
||||
/// 存储本地文件
|
||||
/// </summary>
|
||||
/// <param name="pPath">存储的第一层目录</param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns>文件全路径</returns>
|
||||
public static async Task<string> StorageLocal(this IFormFile file, string pPath = "imports")
|
||||
{
|
||||
string uploadFileFolder = App.WebHostEnvironment?.WebRootPath ?? "wwwroot"!;//赋值路径
|
||||
var now = CommonUtils.GetSingleId();
|
||||
var filePath = Path.Combine(uploadFileFolder, pPath);
|
||||
if (!Directory.Exists(filePath))//如果不存在就创建文件夹
|
||||
Directory.CreateDirectory(filePath);
|
||||
//var fileSuffix = Path.GetExtension(file.Name).ToLower();// 文件后缀
|
||||
var fileObjectName = $"{now}{file.Name}";//存储后的文件名
|
||||
var fileName = Path.Combine(filePath, fileObjectName);//获取文件全路径
|
||||
fileName = fileName.Replace("\\", "/");//格式化一系
|
||||
//存储文件
|
||||
using (var stream = File.Create(Path.Combine(filePath, fileObjectName)))
|
||||
{
|
||||
await file.CopyToAsync(stream).ConfigureAwait(false);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -209,16 +209,10 @@ public static class SqlSugarExtensions
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new()
|
||||
public static Task<int> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
|
||||
{
|
||||
return await db.Updateable(updateObjs).ExecuteCommandAsync().ConfigureAwait(false) > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
|
||||
{
|
||||
return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
|
||||
.ExecuteCommandAsync().ConfigureAwait(false) > 0;
|
||||
return db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
private static IEnumerable<T> Sort<T>(this IEnumerable<T> list, BasePageInput basePageInput)
|
||||
|
@@ -254,6 +254,8 @@ public static class SpecificationDocumentBuilder
|
||||
/// <param name="configure">自定义配置</param>
|
||||
internal static void BuildGen(SwaggerGenOptions swaggerGenOptions, Action<SwaggerGenOptions> configure = null)
|
||||
{
|
||||
|
||||
|
||||
// 创建分组文档
|
||||
CreateSwaggerDocs(swaggerGenOptions);
|
||||
|
||||
@@ -411,7 +413,7 @@ public static class SpecificationDocumentBuilder
|
||||
// 本地函数
|
||||
static string DefaultSchemaIdSelector(Type modelType)
|
||||
{
|
||||
var modelName = modelType.Name;
|
||||
var modelName = modelType.FullName;
|
||||
|
||||
// 处理泛型类型问题
|
||||
if (modelType.IsConstructedGenericType)
|
||||
|
@@ -31,7 +31,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET9Version)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -60,6 +60,9 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
Name = str;
|
||||
else
|
||||
Name = $"Pool<{typeof(T).Name}>";
|
||||
|
||||
// 启动定期清理的定时器
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
/// <summary>销毁</summary>
|
||||
@@ -227,8 +230,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
|
||||
Interlocked.Increment(ref _FreeCount);
|
||||
|
||||
// 启动定期清理的定时器
|
||||
StartTimer();
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
45
src/Admin/ThingsGateway.NewLife.X/Common/BoundedQueue.cs
Normal file
45
src/Admin/ThingsGateway.NewLife.X/Common/BoundedQueue.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class BoundedQueue<T> : IEnumerable<T>
|
||||
{
|
||||
private readonly Queue<T> _queue;
|
||||
private readonly int _capacity;
|
||||
private readonly object _syncRoot = new object();
|
||||
|
||||
public BoundedQueue(int capacity)
|
||||
{
|
||||
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
_capacity = capacity;
|
||||
_queue = new Queue<T>(capacity);
|
||||
}
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_queue.Count == _capacity)
|
||||
_queue.Dequeue();
|
||||
_queue.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { lock (_syncRoot) return _queue.Count; }
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return new List<T>(_queue).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
public class ExpiringDictionary<TKey, TValue> : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<TKey, TValue> _dict = new();
|
||||
private readonly TimerX _cleanupTimer;
|
||||
|
||||
public ExpiringDictionary(int cleanupInterval = 600000)
|
||||
{
|
||||
_cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true };
|
||||
}
|
||||
|
||||
public void TryAdd(TKey key, TValue value)
|
||||
{
|
||||
_dict.TryAdd(key, value);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return _dict.TryGetValue(key, out value);
|
||||
}
|
||||
public TValue GetOrAdd(TKey key, Func<TKey, TValue> func)
|
||||
{
|
||||
return _dict.GetOrAdd(key, func);
|
||||
}
|
||||
public TValue GetOrAdd(TKey key, TValue value)
|
||||
{
|
||||
return _dict.GetOrAdd(key, value);
|
||||
}
|
||||
|
||||
public bool TryRemove(TKey key) => _dict.TryRemove(key, out _);
|
||||
|
||||
public void Clear() => _dict.Clear();
|
||||
|
||||
private void Clear(object? state)
|
||||
{
|
||||
_dict.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dict.Clear();
|
||||
_cleanupTimer.Dispose();
|
||||
}
|
||||
}
|
@@ -103,6 +103,54 @@ public static class Runtime
|
||||
|
||||
/// <summary>是否OSX环境</summary>
|
||||
public static Boolean OSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
public static Boolean? isLegacyWindows;
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否老系统 (Vista/2008/7/2008R2)
|
||||
/// </summary>
|
||||
public static Boolean IsLegacyWindows
|
||||
{
|
||||
get
|
||||
{
|
||||
if (isLegacyWindows != null) return isLegacyWindows.Value;
|
||||
|
||||
if (Windows == false)
|
||||
{
|
||||
isLegacyWindows = false;
|
||||
return isLegacyWindows.Value;
|
||||
}
|
||||
var version = Environment.OSVersion.Version;
|
||||
|
||||
// 如果能拿到真实的 6.x 就直接判断
|
||||
if (version.Major == 6 && version.Minor <= 1)
|
||||
{
|
||||
isLegacyWindows = true;
|
||||
return isLegacyWindows.Value;
|
||||
}
|
||||
if (version.Major < 6)
|
||||
{
|
||||
isLegacyWindows = true;
|
||||
return isLegacyWindows.Value;
|
||||
}
|
||||
|
||||
// 如果拿到的是 10.0(Win8.1 之后有虚拟化问题),用 OSDescription 来兜底
|
||||
var desc = RuntimeInformation.OSDescription;
|
||||
// desc 示例: "Microsoft Windows 6.1.7601" (Win7/2008R2)
|
||||
if (desc.Contains("Windows 6.0") || desc.Contains("Windows 6.1"))
|
||||
{
|
||||
isLegacyWindows = true;
|
||||
return isLegacyWindows.Value;
|
||||
}
|
||||
isLegacyWindows = false;
|
||||
return isLegacyWindows.Value;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
/// <summary>是否Web环境</summary>
|
||||
public static Boolean IsWeb => !String.IsNullOrEmpty(System.Web.HttpRuntime.AppDomainAppId);
|
||||
@@ -115,6 +163,8 @@ public static class Runtime
|
||||
|
||||
/// <summary>是否OSX环境</summary>
|
||||
public static Boolean OSX { get; } = Environment.OSVersion.Platform == PlatformID.MacOSX;
|
||||
|
||||
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
|
@@ -21,6 +21,7 @@ namespace ThingsGateway.NewLife.Json.Extension;
|
||||
/// </summary>
|
||||
public static class SystemTextJsonExtension
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 默认Json规则(带缩进)
|
||||
/// </summary>
|
||||
@@ -31,37 +32,51 @@ public static class SystemTextJsonExtension
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions NoneIndentedOptions;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 默认Json规则(带缩进)
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions IgnoreNullIndentedOptions;
|
||||
|
||||
/// <summary>
|
||||
/// 默认Json规则(无缩进)
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions IgnoreNullNoneIndentedOptions;
|
||||
|
||||
public static JsonSerializerOptions GetOptions(bool writeIndented, bool ignoreNull)
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = writeIndented,
|
||||
DefaultIgnoreCondition = ignoreNull
|
||||
? JsonIgnoreCondition.WhenWritingNull
|
||||
: JsonIgnoreCondition.Never,
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||
};
|
||||
|
||||
options.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
|
||||
options.Converters.Add(new JTokenSystemTextJsonConverter());
|
||||
options.Converters.Add(new JValueSystemTextJsonConverter());
|
||||
options.Converters.Add(new JObjectSystemTextJsonConverter());
|
||||
options.Converters.Add(new JArraySystemTextJsonConverter());
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
static SystemTextJsonExtension()
|
||||
{
|
||||
IndentedOptions = new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = true, // 缩进
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 忽略 null
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||
};
|
||||
// 如有自定义Converter,这里添加
|
||||
// IndentedOptions.Converters.Add(new ByteArrayJsonConverter());
|
||||
IndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
|
||||
IndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter());
|
||||
IndentedOptions.Converters.Add(new JValueSystemTextJsonConverter());
|
||||
IndentedOptions.Converters.Add(new JObjectSystemTextJsonConverter());
|
||||
IndentedOptions.Converters.Add(new JArraySystemTextJsonConverter());
|
||||
NoneIndentedOptions = new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = false, // 不缩进
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||
};
|
||||
NoneIndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
|
||||
NoneIndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter());
|
||||
NoneIndentedOptions.Converters.Add(new JValueSystemTextJsonConverter());
|
||||
NoneIndentedOptions.Converters.Add(new JObjectSystemTextJsonConverter());
|
||||
NoneIndentedOptions.Converters.Add(new JArraySystemTextJsonConverter());
|
||||
// NoneIndentedOptions.Converters.Add(new ByteArrayJsonConverter());
|
||||
|
||||
IndentedOptions = GetOptions(true, false);
|
||||
NoneIndentedOptions = GetOptions(false, false);
|
||||
|
||||
IgnoreNullIndentedOptions = GetOptions(true, true);
|
||||
IgnoreNullNoneIndentedOptions = GetOptions(false, true);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化
|
||||
/// </summary>
|
||||
@@ -96,17 +111,17 @@ public static class SystemTextJsonExtension
|
||||
/// <summary>
|
||||
/// 序列化
|
||||
/// </summary>
|
||||
public static string ToSystemTextJsonString(this object item, bool indented = true)
|
||||
public static string ToSystemTextJsonString(this object item, bool indented = true, bool ignoreNull = true)
|
||||
{
|
||||
return JsonSerializer.Serialize(item, item?.GetType() ?? typeof(object), indented ? IndentedOptions : NoneIndentedOptions);
|
||||
return JsonSerializer.Serialize(item, item?.GetType() ?? typeof(object), ignoreNull ? indented ? IgnoreNullIndentedOptions : IgnoreNullNoneIndentedOptions : indented ? IndentedOptions : NoneIndentedOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化
|
||||
/// </summary>
|
||||
public static byte[] ToSystemTextJsonUtf8Bytes(this object item, bool indented = true)
|
||||
public static byte[] ToSystemTextJsonUtf8Bytes(this object item, bool indented = true, bool ignoreNull = true)
|
||||
{
|
||||
return JsonSerializer.SerializeToUtf8Bytes(item, item.GetType(), indented ? IndentedOptions : NoneIndentedOptions);
|
||||
return JsonSerializer.SerializeToUtf8Bytes(item, item.GetType(), ignoreNull ? indented ? IgnoreNullIndentedOptions : IgnoreNullNoneIndentedOptions : indented ? IndentedOptions : NoneIndentedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -76,6 +76,65 @@ public static class PathHelper
|
||||
#endregion
|
||||
|
||||
#region 路径操作辅助
|
||||
|
||||
public static string GetRelativePath(string relativeTo, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relativeTo))
|
||||
throw new ArgumentNullException(nameof(relativeTo));
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
|
||||
// 转为绝对路径
|
||||
relativeTo = Path.GetFullPath(relativeTo);
|
||||
path = Path.GetFullPath(path);
|
||||
|
||||
// 末尾确保有分隔符,便于处理目录
|
||||
if (!relativeTo.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
relativeTo += Path.DirectorySeparatorChar;
|
||||
|
||||
// 相同路径
|
||||
if (string.Equals(relativeTo, path, StringComparison.OrdinalIgnoreCase))
|
||||
return ".";
|
||||
|
||||
// 检查 UNC 或不同盘符
|
||||
bool isUnc = relativeTo.StartsWith(@"\\") && path.StartsWith(@"\\");
|
||||
if (!isUnc)
|
||||
{
|
||||
// 不同盘符直接返回绝对路径
|
||||
if (!string.Equals(Path.GetPathRoot(relativeTo), Path.GetPathRoot(path), StringComparison.OrdinalIgnoreCase))
|
||||
return path;
|
||||
}
|
||||
|
||||
// 找到共同前缀长度
|
||||
int length = Math.Min(relativeTo.Length, path.Length);
|
||||
int lastSeparatorIndex = -1;
|
||||
int i;
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
if (char.ToLowerInvariant(relativeTo[i]) != char.ToLowerInvariant(path[i]))
|
||||
break;
|
||||
if (relativeTo[i] == Path.DirectorySeparatorChar)
|
||||
lastSeparatorIndex = i;
|
||||
}
|
||||
|
||||
// 计算上退的 ".."
|
||||
string up = "";
|
||||
for (int j = lastSeparatorIndex + 1; j < relativeTo.Length; j++)
|
||||
{
|
||||
if (relativeTo[j] == Path.DirectorySeparatorChar)
|
||||
up += ".." + Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
// 获取剩余下行部分
|
||||
string down = path.Substring(lastSeparatorIndex + 1);
|
||||
|
||||
// 拼接结果
|
||||
string result = up + down;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static String GetPath(String path, Int32 mode)
|
||||
{
|
||||
// 处理路径分隔符,兼容Windows和Linux
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ThingsGateway.NewLife.Reflection;
|
||||
|
||||
@@ -552,19 +553,65 @@ public static class Reflect
|
||||
// return false;
|
||||
//}
|
||||
|
||||
|
||||
private static class DelegateCache<TFunc>
|
||||
{
|
||||
public static readonly ExpiringDictionary<DelegateCacheKey, TFunc> Cache = new();
|
||||
}
|
||||
|
||||
/// <summary>把一个方法转为泛型委托,便于快速反射调用</summary>
|
||||
/// <typeparam name="TFunc"></typeparam>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
public static TFunc? As<TFunc>(this MethodInfo method, Object? target = null)
|
||||
public static TFunc? As<TFunc>(this MethodInfo method, object? target = null)
|
||||
{
|
||||
if (method == null) return default;
|
||||
|
||||
if (target == null)
|
||||
return (TFunc?)(Object?)Delegate.CreateDelegate(typeof(TFunc), method, true);
|
||||
else
|
||||
return (TFunc?)(Object?)Delegate.CreateDelegate(typeof(TFunc), target, method, true);
|
||||
var key = new DelegateCacheKey(method, typeof(TFunc), target);
|
||||
|
||||
var func = DelegateCache<TFunc>.Cache.GetOrAdd(
|
||||
key,
|
||||
_ => (TFunc)(object)(
|
||||
target == null
|
||||
? Delegate.CreateDelegate(typeof(TFunc), method, true)
|
||||
: Delegate.CreateDelegate(typeof(TFunc), target, method, true)));
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
private readonly struct DelegateCacheKey : IEquatable<DelegateCacheKey>
|
||||
{
|
||||
public readonly MethodInfo Method;
|
||||
public readonly Type FuncType;
|
||||
public readonly object? Target;
|
||||
|
||||
public DelegateCacheKey(MethodInfo method, Type funcType, object? target)
|
||||
{
|
||||
Method = method;
|
||||
FuncType = funcType;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public bool Equals(DelegateCacheKey other) =>
|
||||
Method.Equals(other.Method)
|
||||
&& FuncType.Equals(other.FuncType)
|
||||
&& ReferenceEquals(Target, other.Target);
|
||||
|
||||
public override bool Equals(object? obj) =>
|
||||
obj is DelegateCacheKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = Method.GetHashCode();
|
||||
hash = (hash * 397) ^ FuncType.GetHashCode();
|
||||
if (Target != null)
|
||||
hash = (hash * 397) ^ RuntimeHelpers.GetHashCode(Target); // 不受对象重写 GetHashCode 影响
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
@@ -190,7 +190,31 @@ public sealed class Crc32 //: HashAlgorithm
|
||||
crc.Update(stream, count);
|
||||
return crc.Value;
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加Sequence进行校验
|
||||
/// </summary>
|
||||
/// <param name="sequence"></param>
|
||||
/// <returns></returns>
|
||||
public Crc32 Update(ReadOnlySequence<byte> sequence)
|
||||
{
|
||||
foreach (var segment in sequence)
|
||||
{
|
||||
Update(segment.Span);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算校验码 (Sequence)
|
||||
/// </summary>
|
||||
/// <param name="sequence"></param>
|
||||
/// <returns></returns>
|
||||
public static UInt32 Compute(ReadOnlySequence<byte> sequence)
|
||||
{
|
||||
var crc = new Crc32();
|
||||
crc.Update(sequence);
|
||||
return crc.Value;
|
||||
}
|
||||
//#region 抽象实现
|
||||
///// <summary>哈希核心</summary>
|
||||
///// <param name="array"></param>
|
||||
|
@@ -168,12 +168,13 @@ public class TimerScheduler : ILogFeature
|
||||
_period = 60_000;
|
||||
foreach (var timer in arr)
|
||||
{
|
||||
if (!timer.Calling && CheckTime(timer, now))
|
||||
if ((timer.Reentrant || !timer.Calling) && CheckTime(timer, now))
|
||||
{
|
||||
// 必须在主线程设置状态,否则可能异步线程还没来得及设置开始状态,主线程又开始了新的一轮调度
|
||||
timer.Calling = true;
|
||||
if (timer.IsAsyncTask)
|
||||
Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
|
||||
ExecuteAsync(timer);
|
||||
//Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
|
||||
else if (!timer.Async)
|
||||
Execute(timer);
|
||||
else
|
||||
@@ -310,13 +311,17 @@ public class TimerScheduler : ILogFeature
|
||||
if (timer.IsValueTask)
|
||||
{
|
||||
var func = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||
await func!(timer.State).ConfigureAwait(false);
|
||||
var task = func!(timer.State);
|
||||
if (!task.IsCompleted)
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
var func = timer.Method.As<Func<Object?, Task>>(target);
|
||||
await func!(timer.State).ConfigureAwait(false);
|
||||
var task = func!(timer.State);
|
||||
if (!task.IsCompleted)
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -72,7 +72,8 @@ public class TimerX : ITimer, ITimerx, IDisposable
|
||||
|
||||
/// <summary>调用中</summary>
|
||||
public Boolean Calling { get; internal set; }
|
||||
|
||||
/// <summary>可重入</summary>
|
||||
public Boolean Reentrant { get; set; } = false;
|
||||
/// <summary>平均耗时。毫秒</summary>
|
||||
public Int32 Cost { get; internal set; }
|
||||
|
||||
|
@@ -23,18 +23,18 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.0" />
|
||||
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.728" />
|
||||
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.1" />
|
||||
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.821" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
|
||||
<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET9Version)" />-->
|
||||
<PackageReference Include="MySqlConnector" Version="2.4.0" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||
<PackageReference Include="TDengine.Connector" Version="3.1.7" />
|
||||
<PackageReference Include="TDengine.Connector" Version="3.1.8" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
|
||||
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.23" />
|
||||
<PackageReference Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
|
||||
<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,11 +1,11 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.10.20</PluginVersion>
|
||||
<ProPluginVersion>10.10.20</ProPluginVersion>
|
||||
<DefaultVersion>10.10.20</DefaultVersion>
|
||||
<AuthenticationVersion>10.10.1</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
|
||||
<PluginVersion>10.11.22</PluginVersion>
|
||||
<ProPluginVersion>10.11.22</ProPluginVersion>
|
||||
<DefaultVersion>10.11.22</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.2</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.2</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.19</NET8Version>
|
||||
<NET9Version>9.0.8</NET9Version>
|
||||
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
|
||||
@@ -28,7 +28,8 @@
|
||||
<AnalysisModeStyle>None</AnalysisModeStyle>
|
||||
|
||||
<NoWarn>
|
||||
CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA2000;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;NETSDK1206
|
||||
CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA2000;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;RCS1102;RCS1194;NETSDK1206
|
||||
|
||||
</NoWarn>
|
||||
<TargetFrameworks>net8.0;</TargetFrameworks>
|
||||
<LangVersion>13.0</LangVersion>
|
||||
|
@@ -82,13 +82,14 @@ public static class CSharpScriptEngineExtension
|
||||
{
|
||||
if (source.IsNullOrEmpty()) return null;
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var exfield = $"{CacheKey}-Exception-{source}";
|
||||
var runScript = Instance.Get<T>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
runScript = Instance.Get<T>(field);
|
||||
if (runScript == null)
|
||||
var hasValue = Instance.TryGetValue<T>(field, out runScript);
|
||||
if (hasValue == false)
|
||||
{
|
||||
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
var _using = new StringBuilder();
|
||||
@@ -111,8 +112,6 @@ public static class CSharpScriptEngineExtension
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
// 动态加载并执行代码
|
||||
runScript = evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<T>(
|
||||
$@"
|
||||
@@ -140,11 +139,22 @@ public static class CSharpScriptEngineExtension
|
||||
string exString = string.Format(CSScriptResource.CSScriptResource.Error1, typeof(T).FullName);
|
||||
throw new(exString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//如果编译失败,应该不重复编译,避免oom
|
||||
Instance.Set<T>(field, null, TimeSpan.FromHours(1));
|
||||
Instance.Set(exfield, ex, TimeSpan.FromHours(1));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Instance.SetExpire(field, TimeSpan.FromHours(1));
|
||||
|
||||
Instance.SetExpire(exfield, TimeSpan.FromHours(1));
|
||||
if (runScript == null)
|
||||
{
|
||||
throw (Instance.Get<Exception>(exfield) ?? new Exception("compilation error"));
|
||||
}
|
||||
return runScript;
|
||||
}
|
||||
|
||||
|
@@ -93,30 +93,38 @@ public static class ExpressionEvaluatorExtension
|
||||
public static ReadWriteExpressions GetOrAddScript(string source)
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var exfield = $"{CacheKey}-Exception-{source}";
|
||||
var runScript = Instance.Get<ReadWriteExpressions>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
if (!source.Contains("return"))
|
||||
var hasValue = Instance.TryGetValue<ReadWriteExpressions>(field, out runScript);
|
||||
if (!hasValue)
|
||||
{
|
||||
source = $"return {source}";//只判断简单脚本中可省略return字符串
|
||||
}
|
||||
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
var _using = new StringBuilder();
|
||||
var _body = new StringBuilder();
|
||||
src.ToList().ForEach(l =>
|
||||
{
|
||||
if (l.StartsWith("using "))
|
||||
|
||||
|
||||
if (!source.Contains("return"))
|
||||
{
|
||||
_using.AppendLine(l);
|
||||
source = $"return {source}";//只判断简单脚本中可省略return字符串
|
||||
}
|
||||
else
|
||||
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
var _using = new StringBuilder();
|
||||
var _body = new StringBuilder();
|
||||
src.ToList().ForEach(l =>
|
||||
{
|
||||
_body.AppendLine(l);
|
||||
}
|
||||
});
|
||||
// 动态加载并执行代码
|
||||
runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<ReadWriteExpressions>(
|
||||
$@"
|
||||
if (l.StartsWith("using "))
|
||||
{
|
||||
_using.AppendLine(l);
|
||||
}
|
||||
else
|
||||
{
|
||||
_body.AppendLine(l);
|
||||
}
|
||||
});
|
||||
// 动态加载并执行代码
|
||||
try
|
||||
{
|
||||
runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<ReadWriteExpressions>(
|
||||
$@"
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
@@ -137,9 +145,26 @@ public static class ExpressionEvaluatorExtension
|
||||
}}
|
||||
}}
|
||||
");
|
||||
Instance.Set(field, runScript);
|
||||
Instance.Set(field, runScript);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//如果编译失败,应该不重复编译,避免oom
|
||||
Instance.Set<ReadWriteExpressions>(field, null, TimeSpan.FromHours(1));
|
||||
Instance.Set(exfield, ex, TimeSpan.FromHours(1));
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Instance.SetExpire(field, TimeSpan.FromHours(1));
|
||||
Instance.SetExpire(exfield, TimeSpan.FromHours(1));
|
||||
if (runScript == null)
|
||||
{
|
||||
throw (Instance.Get<Exception>(exfield) ?? new Exception("compilation error"));
|
||||
}
|
||||
return runScript;
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,7 @@ public partial class ChannelComponent : ComponentBase
|
||||
await Channel.SetupAsync(config);
|
||||
}
|
||||
|
||||
await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, default);
|
||||
await Channel.ConnectAsync(default);
|
||||
|
||||
if (OnConnectClick.HasDelegate)
|
||||
await OnConnectClick.InvokeAsync(Channel);
|
||||
|
@@ -8,7 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public class ValueTransformConfig
|
||||
{
|
@@ -1,9 +1,5 @@
|
||||
@namespace ThingsGateway.Gateway.Razor
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@namespace ThingsGateway.Debug
|
||||
@using ThingsGateway.Foundation
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@inherits ComponentDefault
|
||||
|
||||
<ValidateForm class="p-4 h-100" Model="@ValueTransformConfig" OnValidSubmit="OnSave">
|
||||
<EditorForm AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=1 LabelWidth=150 Model="ValueTransformConfig">
|
@@ -14,7 +14,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class ValueTransformConfigPage
|
||||
{
|
||||
@@ -205,5 +205,10 @@ public partial class ValueTransformConfigPage
|
||||
}
|
||||
}
|
||||
|
||||
[Inject]
|
||||
ToastService ToastService { get; set; }
|
||||
|
||||
[Inject]
|
||||
IStringLocalizer<ThingsGateway.Razor._Imports> RazorLocalizer { get; set; }
|
||||
#endregion 修改
|
||||
}
|
@@ -1,4 +1,23 @@
|
||||
{
|
||||
|
||||
|
||||
"ThingsGateway.Debug.ValueTransformType": {
|
||||
"None": "None",
|
||||
"Linear": "Linear",
|
||||
"Sqrt": "Sqrt"
|
||||
},
|
||||
|
||||
"ThingsGateway.Debug.ValueTransformConfig": {
|
||||
"TransformType": "TransformType",
|
||||
"MinMax": "MinMax",
|
||||
"ClampToRawRange": "ClampToRawRange",
|
||||
"DecimalPlaces": "DecimalPlaces",
|
||||
"RawMin": "RawMin",
|
||||
"RawMax": "RawMax",
|
||||
"ActualMin": "ActualMin",
|
||||
"ActualMax": "ActualMax"
|
||||
},
|
||||
|
||||
"ThingsGateway.Debug.ChannelComponent": {
|
||||
"BaudRate": "Baud Rate",
|
||||
"BindUrl": "Local Bind IP Address",
|
||||
|
@@ -1,4 +1,21 @@
|
||||
{
|
||||
|
||||
"ThingsGateway.Debug.ValueTransformType": {
|
||||
"None": "无",
|
||||
"Linear": "线性",
|
||||
"Sqrt": "开方"
|
||||
},
|
||||
"ThingsGateway.Debug.ValueTransformConfig": {
|
||||
"TransformType": "转换方式",
|
||||
"MinMax": "最小最大值",
|
||||
"ClampToRawRange": "限制范围",
|
||||
"DecimalPlaces": "保留小数位",
|
||||
"RawMin": "原始最小值",
|
||||
"RawMax": "原始最大值",
|
||||
"ActualMin": "实际最小值",
|
||||
"ActualMax": "实际最大值"
|
||||
},
|
||||
|
||||
"ThingsGateway.Debug.ChannelComponent": {
|
||||
"BaudRate": "波特率",
|
||||
"BindUrl": "本地url",
|
||||
|
@@ -26,7 +26,7 @@ public class PlatformService : IPlatformService
|
||||
|
||||
public async Task OnLogExport(string logPath)
|
||||
{
|
||||
var files = TextFileReader.GetLogFilesAsync(logPath);
|
||||
var files = TextFileReader.GetLogFiles(logPath);
|
||||
if (!files.IsSuccess)
|
||||
{
|
||||
return;
|
||||
|
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
@@ -16,12 +18,12 @@ namespace ThingsGateway.Foundation;
|
||||
public abstract class DDPMessage : MessageBase, IResultMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int HeaderLength => 4;
|
||||
public override long HeaderLength => 4;
|
||||
public byte Type = 0;
|
||||
public string Id;
|
||||
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
Id = byteBlock.ToString(byteBlock.Position, 11).Replace("\0", "");
|
||||
Id = byteBlock.ToString(byteBlock.BytesRead, 11).Replace("\0", "");
|
||||
OperCode = 0;
|
||||
|
||||
Content = GetContent(ref byteBlock);
|
||||
@@ -44,31 +46,31 @@ public abstract class DDPMessage : MessageBase, IResultMessage
|
||||
}
|
||||
}
|
||||
|
||||
public abstract int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
|
||||
public abstract byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
|
||||
public abstract long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
|
||||
public abstract byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
|
||||
}
|
||||
|
||||
public class DDPTcpMessage : DDPMessage
|
||||
{
|
||||
public override int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
|
||||
public override long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
return ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big) - 4;
|
||||
}
|
||||
|
||||
public override byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
return byteBlock.Span.Slice(byteBlock.Position + 11, BodyLength - 12).ToArray();
|
||||
return byteBlock.TotalSequence.Slice(byteBlock.BytesRead + 11, BodyLength - 12).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class DDPUdpMessage : DDPMessage
|
||||
{
|
||||
public override int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
|
||||
public override long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
return byteBlock.Length - 4;
|
||||
return (byteBlock.BytesRead + byteBlock.BytesRemaining - 4);
|
||||
}
|
||||
public override byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
return byteBlock.Span.Slice(byteBlock.Position + 12, BodyLength - 12).ToArray();
|
||||
return byteBlock.TotalSequence.Slice(byteBlock.BytesRead + 12, BodyLength - 12).ToArray();
|
||||
}
|
||||
}
|
@@ -25,6 +25,7 @@ public class DDPSend : ISendMessage
|
||||
string Id;
|
||||
byte Command;
|
||||
bool Tcp;
|
||||
|
||||
public DDPSend(ReadOnlyMemory<byte> readOnlyMemory, string id, bool tcp, byte command = 0x89)
|
||||
{
|
||||
Tcp = tcp;
|
||||
@@ -32,7 +33,8 @@ public class DDPSend : ISendMessage
|
||||
Id = id;
|
||||
Command = command;
|
||||
}
|
||||
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockWriter
|
||||
|
||||
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesWriter
|
||||
{
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)0x7b);
|
||||
WriterExtension.WriteValue(ref byteBlock, (byte)Command);
|
||||
|
@@ -10,8 +10,6 @@
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
using TouchSocket.Resources;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -33,50 +31,120 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
||||
DDPAdapter.Config(Config);
|
||||
}
|
||||
|
||||
// 将当前实例的日志记录器和加载回调设置到适配器中
|
||||
DDPAdapter.Logger = Logger;
|
||||
DDPAdapter.OnLoaded(this);
|
||||
|
||||
DDPAdapter.SendAsyncCallBack = DDPSendAsync;
|
||||
DDPAdapter.ReceivedAsyncCallBack = DDPHandleReceivedData;
|
||||
DataHandlingAdapter.SendAsyncCallBack = DefaultSendAsync;
|
||||
return base.OnTcpConnected(e);
|
||||
}
|
||||
protected Task DefaultSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
return DDPAdapter.SendInputAsync(new DDPSend(memory, Id, true), cancellationToken);
|
||||
}
|
||||
protected Task DDPSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
return base.ProtectedDefaultSendAsync(memory, cancellationToken);
|
||||
}
|
||||
|
||||
private DDPMessage DDPMessage { get; set; }
|
||||
private Task DDPHandleReceivedData(IByteBlockReader byteBlock, IRequestInfo requestInfo)
|
||||
#region 发送
|
||||
|
||||
/// <summary>
|
||||
/// 异步发送数据,通过适配器模式灵活处理数据发送。
|
||||
/// </summary>
|
||||
/// <param name="memory">待发送的只读字节内存块。</param>
|
||||
/// <param name="token">可取消令箭</param>
|
||||
/// <returns>一个异步任务,表示发送操作。</returns>
|
||||
protected virtual async Task NewProtectedSendAsync(ReadOnlyMemory<byte> memory, CancellationToken token)
|
||||
{
|
||||
if (requestInfo is DDPMessage dDPMessage)
|
||||
DDPMessage = dDPMessage;
|
||||
this.ThrowIfDisposed();
|
||||
this.ThrowIfClientNotConnected();
|
||||
|
||||
return EasyTask.CompletedTask;
|
||||
}
|
||||
|
||||
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
|
||||
private WaitLock _waitLock = new(nameof(DDPTcpSessionClientChannel));
|
||||
if (!await this.OnTcpSending(memory).ConfigureAwait(false)) return;
|
||||
|
||||
protected override async ValueTask<bool> OnTcpReceiving(IByteBlockReader byteBlock)
|
||||
{
|
||||
DDPMessage? message = null;
|
||||
var transport = this.Transport;
|
||||
var adapter = this.DataHandlingAdapter;
|
||||
var locker = transport.SemaphoreSlimForWriter;
|
||||
|
||||
await locker.WaitAsync(token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _waitLock.WaitAsync().ConfigureAwait(false);
|
||||
await DDPAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(false);
|
||||
|
||||
message = DDPMessage;
|
||||
DDPMessage = null;
|
||||
// 如果数据处理适配器未设置,则使用默认发送方式。
|
||||
if (adapter == null)
|
||||
{
|
||||
await transport.Output.WriteAsync(memory, token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var byteBlock = new ByteBlock(1024);
|
||||
var ddpSend = new DDPSend(memory, Id, true);
|
||||
ddpSend.Build(ref byteBlock);
|
||||
var newMemory = byteBlock.Memory;
|
||||
var writer = new PipeBytesWriter(transport.Output);
|
||||
adapter.SendInput(ref writer, in newMemory);
|
||||
await writer.FlushAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_waitLock.Release();
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发送请求信息的受保护方法。
|
||||
///
|
||||
/// 此方法首先检查当前对象是否能够发送请求信息,如果不能,则抛出异常。
|
||||
/// 如果可以发送,它将使用数据处理适配器来异步发送输入请求。
|
||||
/// </summary>
|
||||
/// <param name="requestInfo">要发送的请求信息。</param>
|
||||
/// <param name="token">可取消令箭</param>
|
||||
/// <returns>返回一个任务,该任务代表异步操作的结果。</returns>
|
||||
protected virtual async Task NewProtectedSendAsync(IRequestInfo requestInfo, CancellationToken token)
|
||||
{
|
||||
// 检查是否具备发送请求的条件,如果不具备则抛出异常
|
||||
this.ThrowIfCannotSendRequestInfo();
|
||||
|
||||
this.ThrowIfDisposed();
|
||||
this.ThrowIfClientNotConnected();
|
||||
|
||||
var transport = this.Transport;
|
||||
var adapter = this.DataHandlingAdapter;
|
||||
var locker = transport.SemaphoreSlimForWriter;
|
||||
|
||||
await locker.WaitAsync(token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var byteBlock = new ByteBlock(1024);
|
||||
if (requestInfo is not IRequestInfoBuilder requestInfoBuilder)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
requestInfoBuilder.Build(ref byteBlock);
|
||||
var ddpSend = new DDPSend(byteBlock.Memory, Id, true);
|
||||
|
||||
var writer = new PipeBytesWriter(transport.Output);
|
||||
adapter.SendInput(ref writer, ddpSend);
|
||||
await writer.FlushAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion 发送
|
||||
public override Task SendAsync(IRequestInfo requestInfo, CancellationToken token = default)
|
||||
{
|
||||
return NewProtectedSendAsync(requestInfo, token);
|
||||
}
|
||||
|
||||
public override Task SendAsync(ReadOnlyMemory<byte> memory, CancellationToken token = default)
|
||||
{
|
||||
return NewProtectedSendAsync(memory, token);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
|
||||
|
||||
protected override async ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock)
|
||||
{
|
||||
|
||||
if (DDPAdapter.TryParseRequest(ref byteBlock, out var message))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message != null)
|
||||
@@ -86,15 +154,15 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
||||
var id = $"ID={message.Id}";
|
||||
if (message.Type == 0x09)
|
||||
{
|
||||
var reader = new ByteBlockReader(message.Content);
|
||||
var reader = new ClassBytesReader(message.Content);
|
||||
|
||||
if (this.DataHandlingAdapter == null)
|
||||
{
|
||||
await this.OnTcpReceived(new ReceivedDataEventArgs(reader, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
await this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -127,16 +195,16 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
||||
}
|
||||
}
|
||||
|
||||
await ResetIdAsync(id).ConfigureAwait(false);
|
||||
await ResetIdAsync(id, ClosedToken).ConfigureAwait(false);
|
||||
|
||||
//发送成功
|
||||
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false);
|
||||
await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false);
|
||||
if (log)
|
||||
Logger?.Info(string.Format(AppResource.DtuConnected, Id));
|
||||
}
|
||||
else if (message.Type == 0x02)
|
||||
{
|
||||
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false);
|
||||
await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false);
|
||||
Logger?.Info(string.Format(AppResource.DtuDisconnecting, Id));
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
await this.CloseAsync().ConfigureAwait(false);
|
||||
|
@@ -36,16 +36,12 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
DDPAdapter.Config(Config);
|
||||
}
|
||||
|
||||
// 将当前实例的日志记录器和加载回调设置到适配器中
|
||||
DDPAdapter.Logger = Logger;
|
||||
|
||||
if (DDPAdapter.Owner != null)
|
||||
{
|
||||
DDPAdapter.OnLoaded(this);
|
||||
}
|
||||
|
||||
DDPAdapter.SendCallBackAsync = DDPSendAsync;
|
||||
DDPAdapter.ReceivedCallBack = DDPHandleReceivedData;
|
||||
DDPAdapter.SendCallBackAsync = base.ProtectedDefaultSendAsync;
|
||||
DataHandlingAdapter.SendCallBackAsync = DefaultSendAsync;
|
||||
}
|
||||
|
||||
@@ -62,22 +58,7 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
}
|
||||
}
|
||||
|
||||
protected Task DDPSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken token)
|
||||
{
|
||||
//获取endpoint
|
||||
return base.ProtectedDefaultSendAsync(endPoint, memory, token);
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<EndPoint, DDPMessage> DDPMessageDict { get; set; } = new();
|
||||
private Task DDPHandleReceivedData(EndPoint endPoint, IByteBlockReader byteBlock, IRequestInfo requestInfo)
|
||||
{
|
||||
if (requestInfo is DDPMessage dDPMessage)
|
||||
{
|
||||
DDPMessageDict.AddOrUpdate(endPoint, dDPMessage);
|
||||
}
|
||||
|
||||
return EasyTask.CompletedTask;
|
||||
}
|
||||
|
||||
private DeviceUdpDataHandleAdapter<DDPUdpMessage> DDPAdapter = new();
|
||||
|
||||
@@ -98,27 +79,14 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
return base.StopAsync(token);
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<EndPoint, WaitLock> _waitLocks = new();
|
||||
|
||||
protected override async ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e)
|
||||
{
|
||||
var byteBlock = e.ByteBlock;
|
||||
var byteBlock = e.Memory;
|
||||
var endPoint = e.EndPoint;
|
||||
DDPMessage? message = null;
|
||||
var waitLock = _waitLocks.GetOrAdd(endPoint, new WaitLock(nameof(DDPUdpSessionChannel)));
|
||||
try
|
||||
{
|
||||
await waitLock.WaitAsync().ConfigureAwait(false);
|
||||
await DDPAdapter.ReceivedInput(endPoint, byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
|
||||
if (DDPMessageDict.TryGetValue(endPoint, out var dDPMessage))
|
||||
message = dDPMessage;
|
||||
DDPMessageDict.TryRemove(endPoint, out _);
|
||||
}
|
||||
finally
|
||||
{
|
||||
waitLock.Release();
|
||||
}
|
||||
if (!DDPAdapter.TryParseRequest(endPoint, byteBlock, out var message))
|
||||
return true;
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
@@ -127,15 +95,13 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
||||
var id = $"ID={message.Id}";
|
||||
if (message.Type == 0x09)
|
||||
{
|
||||
var reader = new ByteBlockReader(message.Content);
|
||||
|
||||
if (this.DataHandlingAdapter == null)
|
||||
{
|
||||
await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, reader, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.DataHandlingAdapter.ReceivedInput(endPoint, reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
await this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -37,7 +37,9 @@ public static class ChannelOptionsExtensions
|
||||
for (int i = 0; i < funcs.Count; i++)
|
||||
{
|
||||
var func = funcs[i];
|
||||
await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false);
|
||||
var task = func.Invoke(clientChannel, e, i == funcs.Count - 1);
|
||||
if (!task.IsCompleted)
|
||||
await task.ConfigureAwait(false);
|
||||
if (e.Handled)
|
||||
{
|
||||
break;
|
||||
@@ -217,7 +219,7 @@ public static class ChannelOptionsExtensions
|
||||
#if NETSTANDARD || NET6_0_OR_GREATER
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||||
{
|
||||
config.UseUdpConnReset();
|
||||
config.SetUdpConnReset(true);
|
||||
}
|
||||
#endif
|
||||
return ddpUdp;
|
||||
@@ -229,7 +231,7 @@ public static class ChannelOptionsExtensions
|
||||
#if NETSTANDARD || NET6_0_OR_GREATER
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||||
{
|
||||
config.UseUdpConnReset();
|
||||
config.SetUdpConnReset(true);
|
||||
}
|
||||
#endif
|
||||
return udpSessionChannel;
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
@@ -59,15 +57,9 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
|
||||
/// </summary>
|
||||
public ChannelEventHandler Stoping { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 主动请求时的等待池
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; }
|
||||
|
||||
void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -22,6 +22,8 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
|
||||
/// </summary>
|
||||
DataHandlingAdapter ReadOnlyDataHandlingAdapter { get; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通道等待池
|
||||
/// </summary>
|
||||
@@ -34,4 +36,6 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
|
||||
/// </summary>
|
||||
/// <param name="adapter">适配器</param>
|
||||
void SetDataHandlingAdapter(DataHandlingAdapter adapter);
|
||||
|
||||
void SetDataHandlingAdapterLogger(ILog log);
|
||||
}
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
@@ -31,13 +29,23 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
}
|
||||
|
||||
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (_deviceDataHandleAdapter != ReadOnlyDataHandlingAdapter && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
_deviceDataHandleAdapter = handleAdapter;
|
||||
}
|
||||
if (_deviceDataHandleAdapter != null)
|
||||
{
|
||||
_deviceDataHandleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
@@ -65,22 +73,25 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock => ChannelOptions.WaitLock;
|
||||
public virtual WaitLock GetLock(string key) => WaitLock;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
|
||||
|
||||
|
||||
//private readonly WaitLock _connectLock = new WaitLock();
|
||||
|
||||
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
|
||||
_deviceDataHandleAdapter = deviceDataHandleAdapter;
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置数据处理适配器。
|
||||
@@ -104,20 +115,17 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
}
|
||||
|
||||
// 设置适配器的日志记录器和加载、接收数据的回调方法。
|
||||
adapter.Logger = Logger;
|
||||
adapter.OnLoaded(this);
|
||||
adapter.ReceivedAsyncCallBack = PrivateHandleReceivedData;
|
||||
//adapter.SendCallBack = this.ProtectedDefaultSend;
|
||||
adapter.SendAsyncCallBack = ProtectedDefaultSendAsync;
|
||||
|
||||
// 将提供的适配器实例设置为当前实例的数据处理适配器。
|
||||
m_dataHandlingAdapter = adapter;
|
||||
}
|
||||
|
||||
private async Task PrivateHandleReceivedData(IByteBlockReader byteBlock, IRequestInfo requestInfo)
|
||||
private Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo)
|
||||
{
|
||||
LastReceivedTime = DateTime.Now;
|
||||
await this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived).ConfigureAwait(false);
|
||||
return this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,7 +162,8 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
return Task.FromResult(Result.Success);
|
||||
}
|
||||
public volatile bool online;
|
||||
public Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
|
||||
public Task ConnectAsync(CancellationToken token)
|
||||
{
|
||||
var cts = m_transport;
|
||||
m_transport = new();
|
||||
@@ -180,8 +189,11 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则,使用适配器的发送方法进行数据发送。
|
||||
return m_dataHandlingAdapter.SendInputAsync(memory, cancellationToken);
|
||||
var byteBlock = new ByteBlock(1024);
|
||||
m_dataHandlingAdapter.SendInput(ref byteBlock, memory);
|
||||
|
||||
byteBlock.SafeDispose();
|
||||
return EasyTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,9 +202,14 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
// 检查是否具备发送请求的条件,如果不具备则抛出异常
|
||||
ThrowIfCannotSendRequestInfo();
|
||||
|
||||
// 使用数据处理适配器异步发送输入请求
|
||||
return m_dataHandlingAdapter.SendInputAsync(requestInfo, cancellationToken);
|
||||
var byteBlock = new ByteBlock(1024);
|
||||
m_dataHandlingAdapter.SendInput(ref byteBlock, requestInfo);
|
||||
|
||||
byteBlock.SafeDispose();
|
||||
return EasyTask.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
private void ThrowIfCannotSendRequestInfo()
|
||||
{
|
||||
if (m_dataHandlingAdapter?.CanSendRequestInfo != true)
|
||||
@@ -200,4 +217,6 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
||||
throw new NotSupportedException($"当前适配器为空或者不支持对象发送。");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -59,19 +59,18 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
public bool DtuIdHex { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
|
||||
public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
|
||||
{
|
||||
var len = HeartbeatByte.Length;
|
||||
if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel)
|
||||
{
|
||||
if (!socket.Id.StartsWith("ID="))
|
||||
{
|
||||
var id = DtuIdHex ? $"ID={e.ByteBlock.Span.ToHexString()}" : $"ID={e.ByteBlock.ToString(0, e.ByteBlock.Length)}";
|
||||
var id = DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.TotalSequence.ToString(Encoding.UTF8)}";
|
||||
if (tcpServiceChannel.TryGetClient(id, out var oldClient))
|
||||
{
|
||||
try
|
||||
{
|
||||
//await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
await oldClient.CloseAsync().ConfigureAwait(false);
|
||||
oldClient.Dispose();
|
||||
}
|
||||
@@ -79,8 +78,9 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
}
|
||||
}
|
||||
await socket.ResetIdAsync(id).ConfigureAwait(false);
|
||||
await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false);
|
||||
client.Logger?.Info(string.Format(AppResource.DtuConnected, id));
|
||||
e.Reader.Advance((int)e.Reader.BytesRemaining);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
try
|
||||
{
|
||||
//await socket.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
await socket.CloseAsync().ConfigureAwait(false);
|
||||
socket.Dispose();
|
||||
}
|
||||
@@ -102,14 +101,15 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
if (HeartbeatByte.Span.SequenceEqual(e.ByteBlock.Memory.Slice(0, len).Span))
|
||||
if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span))
|
||||
{
|
||||
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
await Task.Delay(200, client.ClosedToken).ConfigureAwait(false);
|
||||
}
|
||||
//回应心跳包
|
||||
await socket.SendAsync(HeartbeatByte, socket.ClosedToken).ConfigureAwait(false);
|
||||
e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining));
|
||||
e.Handled = true;
|
||||
if (socket.Logger?.LogLevel <= LogLevel.Trace)
|
||||
socket.Logger?.Trace($"{socket}- Heartbeat");
|
||||
@@ -118,4 +118,6 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
}
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -105,7 +105,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
}
|
||||
}
|
||||
}
|
||||
private Task Task;
|
||||
private Task _task;
|
||||
private bool SendHeartbeat;
|
||||
public int HeartbeatTime { get; set; } = 3000;
|
||||
|
||||
@@ -125,17 +125,17 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
{
|
||||
await tcpClient.SendAsync(DtuIdByte, tcpClient.ClosedToken).ConfigureAwait(false);
|
||||
|
||||
if (Task == null)
|
||||
if (_task == null)
|
||||
{
|
||||
Task = Task.Factory.StartNew(async () =>
|
||||
_task = Task.Run(async () =>
|
||||
{
|
||||
var failedCount = 0;
|
||||
while (SendHeartbeat)
|
||||
{
|
||||
await Task.Delay(HeartbeatTime).ConfigureAwait(false);
|
||||
await Task.Delay(HeartbeatTime, client.ClosedToken).ConfigureAwait(false);
|
||||
if (!client.Online)
|
||||
{
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -159,15 +159,15 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
}
|
||||
}
|
||||
|
||||
Task = null;
|
||||
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
|
||||
_task = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await e.InvokeNext().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
|
||||
public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
|
||||
{
|
||||
if (client is ITcpSessionClient)
|
||||
{
|
||||
@@ -181,8 +181,9 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
var len = HeartbeatByte.Length;
|
||||
if (len > 0)
|
||||
{
|
||||
if (HeartbeatByte.Span.SequenceEqual(e.ByteBlock.Memory.Slice(0, len).Span))
|
||||
if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span))
|
||||
{
|
||||
e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining));
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -190,6 +191,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Task OnTcpClosed(ITcpSession client, ClosedEventArgs e)
|
||||
{
|
||||
SendHeartbeat = false;
|
||||
|
@@ -77,10 +77,9 @@ public static class PluginUtil
|
||||
a.UseTcpSessionCheckClear()
|
||||
.SetCheckClearType(CheckClearType.All)
|
||||
.SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime))
|
||||
.SetOnClose(async (c, t) =>
|
||||
.SetOnClose((c, t) =>
|
||||
{
|
||||
//await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
await c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout").ConfigureAwait(false);
|
||||
return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout");
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
@@ -34,7 +32,6 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
@@ -50,6 +47,26 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
|
||||
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (_deviceDataHandleAdapter != ProtectedDataHandlingAdapter && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
_deviceDataHandleAdapter = handleAdapter;
|
||||
}
|
||||
if (_deviceDataHandleAdapter != null)
|
||||
{
|
||||
_deviceDataHandleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
|
||||
_deviceDataHandleAdapter = deviceDataHandleAdapter;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; } = new();
|
||||
@@ -65,14 +82,13 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock => ChannelOptions.WaitLock;
|
||||
public virtual WaitLock GetLock(string key) => WaitLock;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
|
||||
|
||||
|
||||
//private readonly WaitLock _connectLock = new WaitLock();
|
||||
/// <inheritdoc/>
|
||||
@@ -86,6 +102,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
if (Online)
|
||||
{
|
||||
PortName = null;
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
@@ -103,7 +120,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
public override async Task ConnectAsync(CancellationToken token)
|
||||
{
|
||||
if (!Online)
|
||||
{
|
||||
@@ -119,7 +136,8 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
if (port != null)
|
||||
PortName = $"{port.PortName}";
|
||||
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.ConnectAsync(token).ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
@@ -134,12 +152,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
private string PortName { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
@@ -153,54 +166,48 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
protected override async Task OnSerialClosed(ClosedEventArgs e)
|
||||
protected override Task OnSerialClosed(ClosedEventArgs e)
|
||||
{
|
||||
Logger?.Info($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
|
||||
await base.OnSerialClosed(e).ConfigureAwait(false);
|
||||
return base.OnSerialClosed(e);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialClosing(ClosingEventArgs e)
|
||||
protected override Task OnSerialClosing(ClosingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
Logger?.Trace($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
await base.OnSerialClosing(e).ConfigureAwait(false);
|
||||
return base.OnSerialClosing(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialConnecting(ConnectingEventArgs e)
|
||||
protected override Task OnSerialConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Trace($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.OnSerialConnecting(e).ConfigureAwait(false);
|
||||
return base.OnSerialConnecting(e);
|
||||
}
|
||||
protected override async Task OnSerialConnected(ConnectedEventArgs e)
|
||||
protected override Task OnSerialConnected(ConnectedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connected");
|
||||
await base.OnSerialConnected(e).ConfigureAwait(false);
|
||||
return base.OnSerialConnected(e);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnSerialReceived(e).ConfigureAwait(false);
|
||||
if (e.RequestInfo is MessageBase response)
|
||||
{
|
||||
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
|
||||
{
|
||||
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
var receivedTask = base.OnSerialReceived(e);
|
||||
if (!receivedTask.IsCompleted)
|
||||
await receivedTask.ConfigureAwait(false);
|
||||
|
||||
if (e.Handled)
|
||||
return;
|
||||
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
}
|
||||
var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived);
|
||||
if (!channelReceivedTask.IsCompleted)
|
||||
await channelReceivedTask.ConfigureAwait(false);
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
WaitHandlePool?.CancelAll();
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -31,9 +29,27 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
|
||||
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
_deviceDataHandleAdapter = handleAdapter;
|
||||
}
|
||||
if (_deviceDataHandleAdapter != null)
|
||||
{
|
||||
_deviceDataHandleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
|
||||
_deviceDataHandleAdapter = deviceDataHandleAdapter;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
|
||||
@@ -62,14 +78,13 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
|
||||
public virtual WaitLock GetLock(string key) => WaitLock;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock => ChannelOptions.WaitLock;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
|
||||
|
||||
|
||||
//private readonly WaitLock _connectLock = new WaitLock();
|
||||
/// <inheritdoc/>
|
||||
@@ -82,6 +97,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
//await _connectLock.WaitAsync().ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
@@ -99,7 +115,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
public override async Task ConnectAsync(CancellationToken token)
|
||||
{
|
||||
if (!Online)
|
||||
{
|
||||
@@ -109,7 +125,8 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
if (!Online)
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.ConnectAsync(token).ConfigureAwait(false);
|
||||
if (Online)
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
@@ -124,12 +141,6 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
@@ -137,58 +148,53 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
return $"{IP}:{Port}";
|
||||
}
|
||||
|
||||
protected override async Task OnTcpClosed(ClosedEventArgs e)
|
||||
protected override Task OnTcpClosed(ClosedEventArgs e)
|
||||
{
|
||||
Logger?.Info($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
|
||||
await base.OnTcpClosed(e).ConfigureAwait(false);
|
||||
return base.OnTcpClosed(e);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(ClosingEventArgs e)
|
||||
protected override Task OnTcpClosing(ClosingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
Logger?.Trace($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
|
||||
await base.OnTcpClosing(e).ConfigureAwait(false);
|
||||
return base.OnTcpClosing(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
|
||||
protected override Task OnTcpConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Trace($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.OnTcpConnecting(e).ConfigureAwait(false);
|
||||
return base.OnTcpConnecting(e);
|
||||
}
|
||||
|
||||
protected override async Task OnTcpConnected(ConnectedEventArgs e)
|
||||
protected override Task OnTcpConnected(ConnectedEventArgs e)
|
||||
{
|
||||
Logger?.Info($"{ToString()} Connected");
|
||||
|
||||
await base.OnTcpConnected(e).ConfigureAwait(false);
|
||||
return base.OnTcpConnected(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(e).ConfigureAwait(false);
|
||||
if (e.RequestInfo is MessageBase response)
|
||||
{
|
||||
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
|
||||
{
|
||||
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
var receivedTask = base.OnTcpReceived(e);
|
||||
if (!receivedTask.IsCompleted)
|
||||
await receivedTask.ConfigureAwait(false);
|
||||
|
||||
if (e.Handled)
|
||||
return;
|
||||
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived);
|
||||
if (!channelReceivedTask.IsCompleted)
|
||||
await channelReceivedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
WaitHandlePool?.CancelAll();
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -23,30 +21,6 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IDevice> Collects { get; } = new();
|
||||
|
||||
///// <summary>
|
||||
///// 停止时是否发送ShutDown
|
||||
///// </summary>
|
||||
//public bool ShutDownEnable { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ClearAsync()
|
||||
{
|
||||
foreach (var client in Clients)
|
||||
{
|
||||
try
|
||||
{
|
||||
//if (ShutDownEnable)
|
||||
// await client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
client.SafeDispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClientDisposeAsync(string id)
|
||||
{
|
||||
if (this.TryGetClient(id, out var client))
|
||||
@@ -136,6 +110,7 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
|
||||
{
|
||||
m_transport?.SafeCancel();
|
||||
m_transport?.SafeDispose();
|
||||
m_transport = null;
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
@@ -209,7 +184,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
|
||||
public Task ConnectAsync(CancellationToken token = default)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return EasyTask.CompletedTask;
|
||||
@@ -228,8 +203,8 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
|
||||
data.ResetSign(MinSign, MaxSign);
|
||||
return data;
|
||||
}
|
||||
public int MaxSign { get; private set; } = 0;
|
||||
public int MinSign { get; private set; } = ushort.MaxValue;
|
||||
public int MinSign { get; private set; } = 0;
|
||||
public int MaxSign { get; private set; } = ushort.MaxValue;
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
MinSign = minSign;
|
||||
@@ -266,24 +241,20 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(TClient socketClient, ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(socketClient, e).ConfigureAwait(false);
|
||||
var receivedTask = base.OnTcpReceived(socketClient, e);
|
||||
if (!receivedTask.IsCompleted)
|
||||
await receivedTask.ConfigureAwait(false);
|
||||
|
||||
if (e.RequestInfo is MessageBase response)
|
||||
{
|
||||
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
|
||||
{
|
||||
await func.Invoke(socketClient, e, ChannelReceived.Count == 1).ConfigureAwait(false);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
if (e.Handled)
|
||||
return;
|
||||
|
||||
await socketClient.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
var channelReceivedTask = socketClient.OnChannelReceivedEvent(e, ChannelReceived);
|
||||
if (!channelReceivedTask.IsCompleted)
|
||||
await channelReceivedTask.ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
|
||||
|
||||
|
||||
IEnumerable<TcpSessionClientChannel> ITcpServiceChannel.Clients => base.Clients;
|
||||
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -23,13 +21,31 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
public TcpSessionClientChannel()
|
||||
{
|
||||
}
|
||||
|
||||
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
_deviceDataHandleAdapter = handleAdapter;
|
||||
}
|
||||
if (_deviceDataHandleAdapter != null)
|
||||
{
|
||||
_deviceDataHandleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
|
||||
_deviceDataHandleAdapter = deviceDataHandleAdapter;
|
||||
}
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
|
||||
@@ -60,7 +76,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new();
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new(0, ushort.MaxValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; internal set; } = new(nameof(TcpSessionClientChannel));
|
||||
@@ -69,25 +85,19 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
public override Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
WaitHandlePool?.CancelAll();
|
||||
return base.CloseAsync(msg, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ConnectAsync(int timeout, CancellationToken token) => Task.CompletedTask;
|
||||
public Task ConnectAsync(CancellationToken token) => Task.CompletedTask;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task SetupAsync(TouchSocketConfig config) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
@@ -98,7 +108,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
WaitHandlePool?.CancelAll();
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
|
||||
@@ -135,18 +145,15 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(e).ConfigureAwait(false);
|
||||
if (e.RequestInfo is MessageBase response)
|
||||
{
|
||||
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
|
||||
{
|
||||
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
var receivedTask = base.OnTcpReceived(e);
|
||||
if (!receivedTask.IsCompleted)
|
||||
await receivedTask.ConfigureAwait(false);
|
||||
|
||||
if (e.Handled)
|
||||
return;
|
||||
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived);
|
||||
if (!channelReceivedTask.IsCompleted)
|
||||
await channelReceivedTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -28,13 +26,31 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
ResetSign();
|
||||
}
|
||||
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
|
||||
|
||||
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
|
||||
public void SetDataHandlingAdapterLogger(ILog log)
|
||||
{
|
||||
if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
|
||||
{
|
||||
_deviceDataHandleAdapter = handleAdapter;
|
||||
}
|
||||
if (_deviceDataHandleAdapter != null)
|
||||
{
|
||||
_deviceDataHandleAdapter.Logger = log;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
|
||||
SetAdapter(udpDataHandlingAdapter);
|
||||
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
|
||||
_deviceDataHandleAdapter = deviceDataHandleAdapter;
|
||||
}
|
||||
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
|
||||
{
|
||||
var pool = WaitHandlePool;
|
||||
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
|
||||
pool?.CancelAll();
|
||||
pool?.SafeDispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -69,14 +85,13 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new();
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new(0, ushort.MaxValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock => ChannelOptions.WaitLock;
|
||||
public virtual WaitLock GetLock(string key) => WaitLock;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<Result> CloseAsync(string msg, CancellationToken token)
|
||||
@@ -85,19 +100,14 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
|
||||
public Task ConnectAsync(CancellationToken token = default)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
await StartAsync().ConfigureAwait(false);
|
||||
return EasyTask.CompletedTask; ;
|
||||
return StartAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
|
||||
SetAdapter(udpDataHandlingAdapter);
|
||||
}
|
||||
|
||||
public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.Token;
|
||||
private CancellationTokenSource m_transport;
|
||||
/// <inheritdoc/>
|
||||
@@ -186,20 +196,17 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnUdpReceived(UdpReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnUdpReceived(e).ConfigureAwait(false);
|
||||
var receivedTask = base.OnUdpReceived(e);
|
||||
if (!receivedTask.IsCompleted)
|
||||
await receivedTask.ConfigureAwait(false);
|
||||
|
||||
if (e.RequestInfo is MessageBase response)
|
||||
{
|
||||
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
|
||||
{
|
||||
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
if (e.Handled)
|
||||
return;
|
||||
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived);
|
||||
if (!channelReceivedTask.IsCompleted)
|
||||
await channelReceivedTask.ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -207,7 +214,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
{
|
||||
m_transport?.SafeCancel();
|
||||
m_transport?.SafeDispose();
|
||||
WaitHandlePool.SafeDispose();
|
||||
m_transport = null;
|
||||
WaitHandlePool?.CancelAll();
|
||||
base.SafetyDispose(disposing);
|
||||
}
|
||||
}
|
||||
|
@@ -10,20 +10,37 @@
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// TCP/Serial适配器基类
|
||||
/// </summary>
|
||||
public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandlingAdapter<TRequest> where TRequest : MessageBase, new()
|
||||
public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingAdapter<TRequest>, IDeviceDataHandleAdapter where TRequest : MessageBase, new()
|
||||
{
|
||||
private ILog logger;
|
||||
|
||||
public new ILog Logger
|
||||
{
|
||||
get => logger ?? base.Logger;
|
||||
set
|
||||
{
|
||||
if (value != logger && value != null)
|
||||
{
|
||||
logger = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="DeviceSingleStreamDataHandleAdapter{TRequest}"/>
|
||||
public DeviceSingleStreamDataHandleAdapter()
|
||||
{
|
||||
CacheTimeoutEnable = true;
|
||||
SurLength = int.MaxValue;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => true;
|
||||
|
||||
@@ -40,11 +57,18 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
public TRequest Request { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetRequest(ISendMessage sendMessage, ref ValueByteBlock byteBlock)
|
||||
public void SetRequest(ISendMessage sendMessage)
|
||||
{
|
||||
if (IsSingleThread)
|
||||
{
|
||||
if (Request != null)
|
||||
{
|
||||
_requestPool.Return(Request);
|
||||
}
|
||||
}
|
||||
var request = GetInstance();
|
||||
request.Sign = sendMessage.Sign;
|
||||
request.SendInfo(sendMessage, ref byteBlock);
|
||||
request.SendInfo(sendMessage);
|
||||
Request = request;
|
||||
}
|
||||
|
||||
@@ -55,24 +79,24 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
|
||||
protected override FilterResult Filter<TReader>(ref TReader byteBlock, bool beCached, ref TRequest request)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.ToHexString(byteBlock.BytesRead, ' ') : byteBlock.ToString(byteBlock.BytesRead))}");
|
||||
|
||||
try
|
||||
{
|
||||
if (IsSingleThread)
|
||||
request = Request == null ? GetInstance() : Request;
|
||||
request = Request == null ? Request = GetInstance() : Request;
|
||||
else
|
||||
{
|
||||
if (!beCached)
|
||||
request = GetInstance();
|
||||
}
|
||||
|
||||
var pos = byteBlock.Position;
|
||||
var pos = byteBlock.BytesRead;
|
||||
|
||||
if (request.HeaderLength > byteBlock.CanReadLength)
|
||||
if (request.HeaderLength > byteBlock.BytesRemaining)
|
||||
{
|
||||
return FilterResult.Cache;//当头部都无法解析时,直接缓存
|
||||
}
|
||||
@@ -80,19 +104,18 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
//检查头部合法性
|
||||
if (request.CheckHead(ref byteBlock))
|
||||
{
|
||||
byteBlock.Position = pos;
|
||||
byteBlock.BytesRead = pos;
|
||||
if (request.BodyLength > MaxPackageSize)
|
||||
{
|
||||
request.OperCode = -1;
|
||||
request.ErrorMessage = $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}";
|
||||
OnError(default, request.ErrorMessage, true, true);
|
||||
SetResult(request);
|
||||
Reset();
|
||||
Logger?.LogWarning($"{ToString()} {request.ErrorMessage}");
|
||||
return FilterResult.GoOn;
|
||||
}
|
||||
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
|
||||
if (request.BodyLength + request.HeaderLength > byteBlock.BytesRemaining)
|
||||
{
|
||||
//body不满足解析,开始缓存,然后保存对象
|
||||
tempCapacity = request.BodyLength + request.HeaderLength;
|
||||
return FilterResult.Cache;
|
||||
}
|
||||
//if (request.BodyLength <= 0)
|
||||
@@ -101,56 +124,48 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
// request.BodyLength = byteBlock.Length;
|
||||
//}
|
||||
var headPos = pos + request.HeaderLength;
|
||||
byteBlock.Position = headPos;
|
||||
byteBlock.BytesRead = headPos;
|
||||
var result = request.CheckBody(ref byteBlock);
|
||||
if (result == FilterResult.Cache)
|
||||
{
|
||||
byteBlock.BytesRead = pos;
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
|
||||
tempCapacity = request.BodyLength + request.HeaderLength;
|
||||
Logger?.Trace($"{ToString()}-Received incomplete, cached message, need length:{request.HeaderLength + request.BodyLength} ,current length:{byteBlock.BytesRead + byteBlock.BytesRemaining} {request?.ErrorMessage}");
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result == FilterResult.GoOn)
|
||||
{
|
||||
var addLen = request.HeaderLength + request.BodyLength;
|
||||
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
|
||||
Logger?.Trace($"{ToString()}-{request?.ToString()}");
|
||||
byteBlock.BytesRead = pos + (addLen > 0 ? addLen : 1);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}-{request?.ToString()}");
|
||||
request.OperCode = -1;
|
||||
SetResult(request);
|
||||
}
|
||||
else if (result == FilterResult.Success)
|
||||
{
|
||||
var addLen = request.HeaderLength + request.BodyLength;
|
||||
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
|
||||
byteBlock.BytesRead = pos + (addLen > 0 ? addLen : 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBlock.Position = pos + 1;//移动游标
|
||||
byteBlock.BytesRead = pos + 1;//移动游标
|
||||
request.OperCode = -1;
|
||||
SetResult(request);
|
||||
return FilterResult.GoOn;//放弃解析
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex, $"{ToString()} Received parsing error");
|
||||
byteBlock.Position = byteBlock.Length;//移动游标
|
||||
byteBlock.BytesRead = byteBlock.BytesRead + byteBlock.BytesRemaining;//移动游标
|
||||
request.Exception = ex;
|
||||
request.OperCode = -1;
|
||||
SetResult(request);
|
||||
return FilterResult.GoOn;//放弃解析
|
||||
}
|
||||
}
|
||||
|
||||
private void SetResult(TRequest request)
|
||||
{
|
||||
if ((Owner as IClientChannel)?.WaitHandlePool?.TryGetDataAsync(request.Sign, out var waitDataAsync) == true)
|
||||
{
|
||||
waitDataAsync.SetResult(request);
|
||||
}
|
||||
}
|
||||
private static ObjectPool<TRequest> _requestPool { get; } = new ObjectPool<TRequest>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取泛型实例。
|
||||
@@ -158,50 +173,45 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
|
||||
/// <returns></returns>
|
||||
protected virtual TRequest GetInstance()
|
||||
{
|
||||
return new TRequest() { OperCode = -1, Sign = -1 };
|
||||
if (IsSingleThread)
|
||||
{
|
||||
var request = _requestPool.Get();
|
||||
request.OperCode = -1;
|
||||
request.Sign = -1;
|
||||
return request;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new TRequest() { OperCode = -1, Sign = -1 };
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnReceivedSuccess(TRequest request)
|
||||
public override void SendInput<TWriter>(ref TWriter writer, in ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
Request = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task PreviewSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
|
||||
//发送
|
||||
await GoSendAsync(memory, cancellationToken).ConfigureAwait(false);
|
||||
writer.Write(memory.Span);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken)
|
||||
public override void SendInput<TWriter>(ref TWriter writer, IRequestInfo requestInfo)
|
||||
{
|
||||
if (!(requestInfo is ISendMessage sendMessage))
|
||||
{
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var byteBlock = new ValueByteBlock(sendMessage.MaxLength);
|
||||
try
|
||||
var span = writer.GetSpan(sendMessage.MaxLength);
|
||||
sendMessage.Build(ref writer);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
{
|
||||
sendMessage.Build(ref byteBlock);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
{
|
||||
SetRequest(sendMessage, ref byteBlock);
|
||||
}
|
||||
await GoSendAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(false);
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? span.Slice(0, (int)writer.WrittenCount).ToHexString(' ') : (span.Slice(0, (int)writer.WrittenCount).ToString(Encoding.UTF8)))}");
|
||||
}
|
||||
finally
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
SetRequest(sendMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -11,13 +11,29 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// UDP适配器基类
|
||||
/// </summary>
|
||||
public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where TRequest : MessageBase, new()
|
||||
public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter, IDeviceDataHandleAdapter where TRequest : MessageBase, new()
|
||||
{
|
||||
private ILog logger;
|
||||
|
||||
public new ILog Logger
|
||||
{
|
||||
get => logger ?? base.Logger;
|
||||
set
|
||||
{
|
||||
if (value != logger && value != null)
|
||||
{
|
||||
logger = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => true;
|
||||
|
||||
@@ -34,11 +50,18 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
public TRequest Request { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetRequest(ISendMessage sendMessage, ref ValueByteBlock byteBlock)
|
||||
public void SetRequest(ISendMessage sendMessage)
|
||||
{
|
||||
if (IsSingleThread)
|
||||
{
|
||||
if (Request != null)
|
||||
{
|
||||
_requestPool.Return(Request);
|
||||
}
|
||||
}
|
||||
var request = GetInstance();
|
||||
request.Sign = sendMessage.Sign;
|
||||
request.SendInfo(sendMessage, ref byteBlock);
|
||||
request.SendInfo(sendMessage);
|
||||
Request = request;
|
||||
}
|
||||
|
||||
@@ -48,122 +71,154 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
return Owner.ToString();
|
||||
}
|
||||
|
||||
private static ObjectPool<TRequest> _requestPool { get; } = new ObjectPool<TRequest>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取泛型实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual TRequest GetInstance()
|
||||
{
|
||||
return new TRequest() { OperCode = -1, Sign = -1 };
|
||||
if (IsSingleThread)
|
||||
{
|
||||
var request = _requestPool.Get();
|
||||
request.OperCode = -1;
|
||||
request.Sign = -1;
|
||||
return request;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new TRequest() { OperCode = -1, Sign = -1 };
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewReceived(EndPoint remoteEndPoint, IByteBlockReader byteBlock)
|
||||
|
||||
#region ParseRequest
|
||||
/// <summary>
|
||||
/// 尝试从字节读取器中解析出请求信息。
|
||||
/// </summary>
|
||||
/// <param name="remoteEndPoint">remoteEndPoint。</param>
|
||||
/// <param name="memory">memory。</param>
|
||||
/// <param name="request">解析出的请求信息。</param>
|
||||
/// <returns>解析成功返回 true,否则返回 false。</returns>
|
||||
public bool TryParseRequest(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory, out TRequest request)
|
||||
{
|
||||
return this.ParseRequestCore(remoteEndPoint, memory, out request);
|
||||
}
|
||||
|
||||
protected virtual bool ParseRequestCore(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory, out TRequest request1)
|
||||
{
|
||||
request1 = null;
|
||||
try
|
||||
{
|
||||
byteBlock.Position = 0;
|
||||
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
|
||||
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? memory.Span.ToHexString(' ') : memory.Span.ToString(Encoding.UTF8))}");
|
||||
|
||||
TRequest request = null;
|
||||
if (IsSingleThread)
|
||||
request = Request == null ? GetInstance() : Request;
|
||||
request = Request == null ? Request = GetInstance() : Request;
|
||||
else
|
||||
{
|
||||
request = GetInstance();
|
||||
}
|
||||
request1 = request;
|
||||
|
||||
var pos = byteBlock.Position;
|
||||
var byteBlock = new ClassBytesReader(memory);
|
||||
byteBlock.BytesRead = 0;
|
||||
|
||||
if (request.HeaderLength > byteBlock.CanReadLength)
|
||||
var pos = byteBlock.BytesRead;
|
||||
|
||||
if (request.HeaderLength > byteBlock.BytesRead + byteBlock.BytesRemaining)
|
||||
{
|
||||
return;//当头部都无法解析时,直接缓存
|
||||
return false;//当头部都无法解析时,直接缓存
|
||||
}
|
||||
|
||||
//检查头部合法性
|
||||
if (request.CheckHead(ref byteBlock))
|
||||
{
|
||||
byteBlock.Position = pos;
|
||||
byteBlock.BytesRead = pos;
|
||||
|
||||
if (request.BodyLength > MaxPackageSize)
|
||||
{
|
||||
OnError(default, $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}", true, true);
|
||||
return;
|
||||
request.OperCode = -1;
|
||||
request.ErrorMessage = $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}";
|
||||
Reset();
|
||||
Logger?.LogWarning($"{ToString()} {request.ErrorMessage}");
|
||||
return false;
|
||||
}
|
||||
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
|
||||
if (request.BodyLength + request.HeaderLength > byteBlock.BytesRead + byteBlock.BytesRemaining)
|
||||
{
|
||||
//body不满足解析,开始缓存,然后保存对象
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
//if (request.BodyLength <= 0)
|
||||
//{
|
||||
// //如果body长度无法确定,直接读取全部
|
||||
// request.BodyLength = byteBlock.Length;
|
||||
//}
|
||||
|
||||
var headPos = pos + request.HeaderLength;
|
||||
byteBlock.Position = headPos;
|
||||
byteBlock.BytesRead = headPos;
|
||||
var result = request.CheckBody(ref byteBlock);
|
||||
if (result == FilterResult.Cache)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
|
||||
Logger?.Trace($"{ToString()}-Received incomplete, cached message, need length:{request.HeaderLength + request.BodyLength} ,current length:{byteBlock.BytesRead + byteBlock.BytesRemaining} {request?.ErrorMessage}");
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result == FilterResult.GoOn)
|
||||
{
|
||||
var addLen = request.HeaderLength + request.BodyLength;
|
||||
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
|
||||
Logger?.Trace($"{ToString()}-{request?.ToString()}");
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}-{request?.ToString()}");
|
||||
request.OperCode = -1;
|
||||
if ((Owner as IClientChannel)?.WaitHandlePool?.TryGetDataAsync(request.Sign, out var waitDataAsync) == true)
|
||||
{
|
||||
waitDataAsync.SetResult(request);
|
||||
}
|
||||
}
|
||||
else if (result == FilterResult.Success)
|
||||
{
|
||||
var addLen = request.HeaderLength + request.BodyLength;
|
||||
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
|
||||
await GoReceived(remoteEndPoint, null, request).ConfigureAwait(false);
|
||||
request1 = request;
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBlock.Position = pos + 1;
|
||||
request.OperCode = -1;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex, $"{ToString()} Received parsing error");
|
||||
byteBlock.Position = byteBlock.Length;//移动游标
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
protected override Task PreviewReceivedAsync(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
if (ParseRequestCore(remoteEndPoint, memory, out var request))
|
||||
{
|
||||
return GoReceived(remoteEndPoint, null, request);
|
||||
}
|
||||
return EasyTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
//发送
|
||||
await GoSendAsync(endPoint, memory, cancellationToken).ConfigureAwait(false);
|
||||
return GoSendAsync(endPoint, memory, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken)
|
||||
protected override Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!(requestInfo is ISendMessage sendMessage))
|
||||
{
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var byteBlock = new ValueByteBlock(sendMessage.MaxLength);
|
||||
try
|
||||
{
|
||||
@@ -173,9 +228,9 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
|
||||
|
||||
if (IsSingleThread)
|
||||
{
|
||||
SetRequest(sendMessage, ref byteBlock);
|
||||
SetRequest(sendMessage);
|
||||
}
|
||||
await GoSendAsync(endPoint, byteBlock.Memory, cancellationToken).ConfigureAwait(false);
|
||||
return GoSendAsync(endPoint, byteBlock.Memory, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@@ -1,46 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 定义了字节块构建器的接口,用于从内存池中构建和管理字节块。
|
||||
/// </summary>
|
||||
public interface IByteBlockWriterBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 构建数据时,指示内存池的申请长度。
|
||||
/// <para>
|
||||
/// 建议:该值可以尽可能的设置大一些,这样可以避免内存池扩容。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
int MaxLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构建对象到<see cref="ByteBlock"/>
|
||||
/// </summary>
|
||||
/// <param name="writer">要构建的字节块对象引用。</param>
|
||||
void Build<TWriter>(ref TWriter writer) where TWriter : IByteBlockWriter
|
||||
#if AllowsRefStruct
|
||||
,allows ref struct
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 指示<see cref="IRequestInfo"/>应当如何构建
|
||||
/// </summary>
|
||||
public interface IRequestInfoByteBlockWriterBuilder : IRequestInfo, IByteBlockWriterBuilder
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace ThingsGateway.Foundation
|
||||
{
|
||||
public interface IDeviceDataHandleAdapter
|
||||
{
|
||||
bool CanSendRequestInfo { get; }
|
||||
bool IsHexLog { get; set; }
|
||||
bool IsSingleThread { get; set; }
|
||||
ILog Logger { get; set; }
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ public interface IResultMessage : IOperResult, IRequestInfo
|
||||
/// <summary>
|
||||
/// 数据体长度
|
||||
/// </summary>
|
||||
int BodyLength { get; set; }
|
||||
long BodyLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 解析的字节信息
|
||||
@@ -28,7 +28,7 @@ public interface IResultMessage : IOperResult, IRequestInfo
|
||||
/// <summary>
|
||||
/// 消息头的指令长度,不固定时返回0
|
||||
/// </summary>
|
||||
int HeaderLength { get; }
|
||||
long HeaderLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 等待标识,对于并发协议,必须从协议中例如固定头部获取标识字段
|
||||
@@ -42,17 +42,17 @@ public interface IResultMessage : IOperResult, IRequestInfo
|
||||
/// <para>然后返回<see cref="FilterResult.GoOn"/></para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功有效</returns>
|
||||
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
|
||||
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
|
||||
|
||||
/// <summary>
|
||||
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
|
||||
/// <para>如果返回false,意味着放弃本次解析的所有数据,包括已经解析完成的Header</para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功的结果</returns>
|
||||
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
|
||||
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
|
||||
|
||||
/// <summary>
|
||||
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法
|
||||
/// </summary>
|
||||
void SendInfo(ISendMessage sendMessage, ref ValueByteBlock byteBlock);
|
||||
void SendInfo(ISendMessage sendMessage);
|
||||
}
|
||||
|
@@ -13,6 +13,6 @@ namespace ThingsGateway.Foundation;
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoByteBlockWriterBuilder
|
||||
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder
|
||||
{
|
||||
}
|
||||
|
@@ -33,28 +33,28 @@ public class MessageBase : OperResultClass<ReadOnlyMemory<byte>>, IResultMessage
|
||||
#endregion 构造
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BodyLength { get; set; }
|
||||
public long BodyLength { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int HeaderLength { get; set; }
|
||||
public virtual long HeaderLength { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int Sign { get; set; } = -1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader
|
||||
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader
|
||||
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SendInfo(ISendMessage sendMessage, ref TouchSocket.Core.ValueByteBlock byteBlock)
|
||||
public virtual void SendInfo(ISendMessage sendMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@@ -1,306 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:https://touchsocket.net/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义数据处理适配器,使用该适配器时,接收方收到的数据中,<see cref="ByteBlock"/>将为<see langword="null"/>,
|
||||
/// 同时<see cref="IRequestInfo"/>将实现为TRequest,发送数据直接发送。
|
||||
/// </summary>
|
||||
public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataHandlingAdapter where TRequest : IRequestInfo
|
||||
{
|
||||
private readonly Type m_requestType;
|
||||
private ValueByteBlock m_tempByteBlock;
|
||||
private TRequest m_tempRequest;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化自定义数据处理适配器。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该构造函数在创建<see cref="TcpCustomDataHandlingAdapter{TRequest}"/>实例时,会指定请求类型。
|
||||
/// </remarks>
|
||||
public TcpCustomDataHandlingAdapter()
|
||||
{
|
||||
this.m_requestType = typeof(TRequest);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => false;
|
||||
|
||||
/// <summary>
|
||||
/// 指示需要解析当前包的剩余长度。如果不能得知,请赋值<see cref="int.MaxValue"/>。
|
||||
/// </summary>
|
||||
protected int SurLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 尝试解析请求数据块。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">字节块类型,必须实现IByteBlock接口。</typeparam>
|
||||
/// <param name="reader">待解析的字节块。</param>
|
||||
/// <param name="request">解析出的请求对象。</param>
|
||||
/// <returns>解析是否成功。</returns>
|
||||
public bool TryParseRequest<TByteBlock>(ref TByteBlock reader, out TRequest request) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
// 检查缓存是否超时,如果超时则清除缓存。
|
||||
if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout)
|
||||
{
|
||||
this.Reset();
|
||||
}
|
||||
|
||||
// 如果临时字节块为空,则尝试直接解析。
|
||||
if (this.m_tempByteBlock.IsEmpty)
|
||||
{
|
||||
return this.Single(ref reader, out request) == FilterResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果剩余长度小于等于0,则抛出异常。
|
||||
if (this.SurLength <= 0)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
// 计算本次可以读取的长度。
|
||||
var len = Math.Min(this.SurLength, reader.CanReadLength);
|
||||
|
||||
// 从输入字节块中读取数据到临时字节块中。
|
||||
var block = this.m_tempByteBlock;
|
||||
block.Write(reader.Span.Slice(reader.Position, len));
|
||||
reader.Position += len;
|
||||
this.SurLength -= len;
|
||||
|
||||
// 重置临时字节块并准备下一次使用。
|
||||
this.m_tempByteBlock = default;
|
||||
|
||||
// 回到字节块的起始位置。
|
||||
block.SeekToStart();
|
||||
try
|
||||
{
|
||||
// 尝试解析字节块。
|
||||
var filterResult = this.Single(ref block, out request);
|
||||
switch (filterResult)
|
||||
{
|
||||
case FilterResult.Cache:
|
||||
{
|
||||
// 如果临时字节块不为空,则继续缓存。
|
||||
if (!this.m_tempByteBlock.IsEmpty)
|
||||
{
|
||||
reader.Position += this.m_tempByteBlock.Length;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FilterResult.Success:
|
||||
{
|
||||
// 如果字节块中还有剩余数据,则回退指针。
|
||||
if (block.CanReadLength > 0)
|
||||
{
|
||||
reader.Position -= block.CanReadLength;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case FilterResult.GoOn:
|
||||
default:
|
||||
// 对于需要继续解析的情况,也回退指针。
|
||||
if (block.CanReadLength > 0)
|
||||
{
|
||||
reader.Position -= block.CanReadLength;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 释放字节块资源。
|
||||
block.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 筛选解析数据。实例化的TRequest会一直保存,直至解析成功,或手动清除。
|
||||
/// <para>当不满足解析条件时,请返回<see cref="FilterResult.Cache"/>,此时会保存<see cref="ByteBlock.CanReadLength"/>的数据</para>
|
||||
/// <para>当数据部分异常时,请移动<see cref="ByteBlock.Position"/>到指定位置,然后返回<see cref="FilterResult.GoOn"/></para>
|
||||
/// <para>当完全满足解析条件时,请返回<see cref="FilterResult.Success"/>最后将<see cref="ByteBlock.Position"/>移至指定位置。</para>
|
||||
/// </summary>
|
||||
/// <param name="reader">字节块</param>
|
||||
/// <param name="beCached">是否为上次遗留对象,当该参数为<see langword="true"/>时,request也将是上次实例化的对象。</param>
|
||||
/// <param name="request">对象。</param>
|
||||
/// <param name="tempCapacity">缓存容量。当需要首次缓存时,指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。</param>
|
||||
/// <returns></returns>
|
||||
protected abstract FilterResult Filter<TByteBlock>(ref TByteBlock reader, bool beCached, ref TRequest request, ref int tempCapacity)
|
||||
where TByteBlock : IByteBlockReader;
|
||||
|
||||
/// <summary>
|
||||
/// 判断请求对象是否应该被缓存。
|
||||
/// </summary>
|
||||
/// <param name="request">请求对象。</param>
|
||||
/// <returns>返回布尔值,指示请求对象是否应该被缓存。</returns>
|
||||
protected virtual bool IsBeCached(in TRequest request)
|
||||
{
|
||||
return this.m_requestType.IsValueType ? request.GetHashCode() != default(TRequest).GetHashCode() : request != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功执行接收以后。
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
protected virtual void OnReceivedSuccess(TRequest request)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 即将执行<see cref="SingleStreamDataHandlingAdapter.GoReceivedAsync(IByteBlockReader, IRequestInfo)"/>。
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns>返回值标识是否继续执行</returns>
|
||||
protected virtual bool OnReceivingSuccess(TRequest request)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <param name="byteBlock"></param>
|
||||
protected override async Task PreviewReceivedAsync(IByteBlockReader byteBlock)
|
||||
{
|
||||
if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout)
|
||||
{
|
||||
this.Reset();
|
||||
}
|
||||
if (this.m_tempByteBlock.IsEmpty)
|
||||
{
|
||||
await this.SingleAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.m_tempByteBlock.Write(byteBlock.Span);
|
||||
using (var block = this.m_tempByteBlock)
|
||||
{
|
||||
this.m_tempByteBlock = default;
|
||||
await this.SingleAsync(block).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Reset()
|
||||
{
|
||||
this.m_tempByteBlock.SafeDispose();
|
||||
this.m_tempByteBlock = default;
|
||||
this.m_tempRequest = default;
|
||||
this.SurLength = 0;
|
||||
base.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理单个字节块,提取请求对象。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">字节块类型,需要实现IByteBlock接口。</typeparam>
|
||||
/// <param name="reader">字节块,将被解析以提取请求对象。</param>
|
||||
/// <param name="request">输出参数,提取出的请求对象。</param>
|
||||
/// <returns>返回过滤结果,指示处理的状态。</returns>
|
||||
protected FilterResult Single<TByteBlock>(ref TByteBlock reader, out TRequest request) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
// 初始化临时缓存容量。
|
||||
var tempCapacity = 1024 * 64;
|
||||
// 执行过滤操作,根据是否应该缓存来决定如何处理字节块和请求对象。
|
||||
var filterResult = this.Filter(ref reader, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity);
|
||||
switch (filterResult)
|
||||
{
|
||||
case FilterResult.Success:
|
||||
// 如果过滤结果是成功,则设置请求对象并重置临时请求对象为默认值。
|
||||
request = this.m_tempRequest;
|
||||
this.m_tempRequest = default;
|
||||
return filterResult;
|
||||
|
||||
case FilterResult.Cache:
|
||||
// 如果过滤结果需要缓存,则创建一个新的字节块来缓存数据。
|
||||
if (reader.CanReadLength > 0)
|
||||
{
|
||||
this.m_tempByteBlock = new ValueByteBlock(tempCapacity);
|
||||
this.m_tempByteBlock.Write(reader.Span.Slice(reader.Position, reader.CanReadLength));
|
||||
|
||||
// 如果缓存的数据长度超过设定的最大包大小,则抛出异常。
|
||||
if (this.m_tempByteBlock.Length > this.MaxPackageSize)
|
||||
{
|
||||
throw new Exception($"The parsed signal was not received when the cached data length {m_tempByteBlock.Length} exceeds the set value {MaxPackageSize}");
|
||||
}
|
||||
|
||||
// 将字节块指针移到末尾。
|
||||
reader.Advance((int)reader.BytesRemaining);
|
||||
}
|
||||
// 更新缓存时间。
|
||||
if (this.UpdateCacheTimeWhenRev)
|
||||
{
|
||||
this.LastCacheTime = DateTimeOffset.UtcNow;
|
||||
}
|
||||
request = default;
|
||||
return filterResult;
|
||||
|
||||
case FilterResult.GoOn:
|
||||
default:
|
||||
// 对于继续或默认的过滤结果,更新缓存时间。
|
||||
if (this.UpdateCacheTimeWhenRev)
|
||||
{
|
||||
this.LastCacheTime = DateTimeOffset.UtcNow;
|
||||
}
|
||||
request = default;
|
||||
return filterResult;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SingleAsync<TByteBlock>(TByteBlock reader) where TByteBlock : IByteBlockReader
|
||||
{
|
||||
reader.Position = 0;
|
||||
while (reader.Position < reader.Length)
|
||||
{
|
||||
if (this.DisposedValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var tempCapacity = 1024 * 64;
|
||||
var filterResult = this.Filter(ref reader, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity);
|
||||
|
||||
switch (filterResult)
|
||||
{
|
||||
case FilterResult.Success:
|
||||
if (this.OnReceivingSuccess(this.m_tempRequest))
|
||||
{
|
||||
await this.GoReceivedAsync(null, this.m_tempRequest).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
this.OnReceivedSuccess(this.m_tempRequest);
|
||||
}
|
||||
this.m_tempRequest = default;
|
||||
break;
|
||||
|
||||
case FilterResult.Cache:
|
||||
this.m_tempByteBlock = new ValueByteBlock(tempCapacity);
|
||||
this.m_tempByteBlock.Write(reader.Span);
|
||||
|
||||
if (this.m_tempByteBlock.Length > this.MaxPackageSize)
|
||||
{
|
||||
this.OnError(default, $"The parsed signal was not received when the cached data length {m_tempByteBlock.Length} exceeds the set value {MaxPackageSize}", true, true);
|
||||
}
|
||||
if (this.UpdateCacheTimeWhenRev)
|
||||
{
|
||||
this.LastCacheTime = DateTimeOffset.UtcNow;
|
||||
}
|
||||
return;
|
||||
|
||||
case FilterResult.GoOn:
|
||||
if (this.UpdateCacheTimeWhenRev)
|
||||
{
|
||||
this.LastCacheTime = DateTimeOffset.UtcNow;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -214,10 +214,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
/// </summary>
|
||||
protected virtual ValueTask<bool> ChannelStarting(IClientChannel channel, bool last)
|
||||
{
|
||||
if (channel.ReadOnlyDataHandlingAdapter != null)
|
||||
{
|
||||
channel.ReadOnlyDataHandlingAdapter.Logger = Logger;
|
||||
}
|
||||
channel.SetDataHandlingAdapterLogger(Logger);
|
||||
return EasyValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
@@ -318,7 +315,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
{
|
||||
try
|
||||
{
|
||||
if (client.WaitHandlePool.SetRun(response))
|
||||
if (client.WaitHandlePool.Set(response))
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -351,11 +348,19 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
|
||||
if (channel is IDtuUdpSessionChannel udpSession)
|
||||
{
|
||||
await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false);
|
||||
var sendTask = udpSession.SendAsync(endPoint, sendMessage, token);
|
||||
if (!sendTask.IsCompleted)
|
||||
{
|
||||
await sendTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendAsync(sendMessage, token).ConfigureAwait(false);
|
||||
var sendTask = channel.SendAsync(sendMessage, token);
|
||||
if (!sendTask.IsCompleted)
|
||||
{
|
||||
await sendTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return OperResult.Success;
|
||||
@@ -366,21 +371,17 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask BefortSendAsync(IClientChannel channel, CancellationToken token)
|
||||
private Task BeforeSendAsync(IClientChannel channel, CancellationToken token)
|
||||
{
|
||||
SetDataAdapter(channel);
|
||||
try
|
||||
if (AutoConnect && Channel != null && Channel?.Online != true)
|
||||
{
|
||||
await ConnectAsync(token).ConfigureAwait(false);
|
||||
return ConnectAsync(token);
|
||||
}
|
||||
catch (Exception)
|
||||
else
|
||||
{
|
||||
await Task.Delay(1000, token).ConfigureAwait(false);
|
||||
throw;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
private WaitLock connectWaitLock = new(nameof(DeviceBase));
|
||||
@@ -397,13 +398,13 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
if (Channel.PluginManager == null)
|
||||
await Channel.SetupAsync(Channel.Config.Clone()).ConfigureAwait(false);
|
||||
await Channel.CloseAsync().ConfigureAwait(false);
|
||||
await Task.Delay(500, token).ConfigureAwait(false);
|
||||
await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, token).ConfigureAwait(false);
|
||||
using var ctsTime = new CancellationTokenSource(Channel.ChannelOptions.ConnectTimeout);
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, token);
|
||||
await Channel.ConnectAsync(cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { await Task.Delay(500, token).ConfigureAwait(false); } catch { }
|
||||
connectWaitLock.Release();
|
||||
}
|
||||
}
|
||||
@@ -421,11 +422,14 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
|
||||
try
|
||||
{
|
||||
await BefortSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false);
|
||||
var beforeSendTask = BeforeSendAsync(channelResult.Content, cancellationToken);
|
||||
if (!beforeSendTask.IsCompleted)
|
||||
{
|
||||
await beforeSendTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (channelResult.Content.ReadOnlyDataHandlingAdapter != null)
|
||||
channelResult.Content.ReadOnlyDataHandlingAdapter.Logger = Logger;
|
||||
channelResult.Content.SetDataHandlingAdapterLogger(Logger);
|
||||
|
||||
EndPoint? endPoint = GetUdpEndpoint(dtuId);
|
||||
|
||||
@@ -438,6 +442,8 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
@@ -513,15 +519,23 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
return SendThenReturnAsync(sendMessage, channelResult.Content, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false);
|
||||
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
|
||||
var sendTask = SendThenReturnMessageAsync(sendMessage, channel, cancellationToken);
|
||||
if (!sendTask.IsCompleted)
|
||||
{
|
||||
var result = await sendTask.ConfigureAwait(false);
|
||||
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = sendTask.Result;
|
||||
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -530,11 +544,11 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected virtual async ValueTask<MessageBase> SendThenReturnMessageAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
|
||||
protected virtual ValueTask<MessageBase> SendThenReturnMessageAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channelResult = GetChannel(this is IDtu dtu ? dtu.DtuId : null);
|
||||
if (!channelResult.IsSuccess) return new MessageBase(channelResult);
|
||||
return await SendThenReturnMessageAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false);
|
||||
if (!channelResult.IsSuccess) return EasyValueTask.FromResult(new MessageBase(channelResult));
|
||||
return SendThenReturnMessageAsync(sendMessage, channelResult.Content, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -553,8 +567,11 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
WaitLock? waitLock = null;
|
||||
try
|
||||
{
|
||||
await BefortSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var beforeSendTask = BeforeSendAsync(clientChannel, cancellationToken);
|
||||
if (!beforeSendTask.IsCompleted)
|
||||
{
|
||||
await beforeSendTask.ConfigureAwait(false);
|
||||
}
|
||||
var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null;
|
||||
waitLock = GetWaitLock(clientChannel, dtuId);
|
||||
|
||||
@@ -565,32 +582,53 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return new MessageBase(new OperationCanceledException());
|
||||
|
||||
if (clientChannel.ReadOnlyDataHandlingAdapter != null)
|
||||
clientChannel.ReadOnlyDataHandlingAdapter.Logger = Logger;
|
||||
clientChannel.SetDataHandlingAdapterLogger(Logger);
|
||||
|
||||
Channel.ChannelReceivedWaitDict.TryAdd(sign, ChannelReceived);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return new MessageBase(new OperationCanceledException());
|
||||
|
||||
var sendOperResult = await SendAsync(command, clientChannel, endPoint, cancellationToken).ConfigureAwait(false);
|
||||
OperResult sendOperResult = default;
|
||||
var sendTask = SendAsync(command, clientChannel, endPoint, cancellationToken);
|
||||
if (!sendTask.IsCompleted)
|
||||
{
|
||||
sendOperResult = await sendTask.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
sendOperResult = sendTask.Result;
|
||||
}
|
||||
|
||||
if (!sendOperResult.IsSuccess)
|
||||
return new MessageBase(sendOperResult);
|
||||
|
||||
using var ctsTime = new CancellationTokenSource(timeout);
|
||||
try
|
||||
{
|
||||
waitData.SetCancellationToken(Channel.ClosedToken);
|
||||
|
||||
await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, Channel.ClosedToken);
|
||||
var waitDataTask = waitData.WaitAsync(cts.Token);
|
||||
if (!waitDataTask.IsCompleted)
|
||||
{
|
||||
await waitDataTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (ctsTime.IsCancellationRequested)
|
||||
{
|
||||
return new MessageBase(new TimeoutException());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
var result = waitData.Check();
|
||||
|
||||
var result = waitData.Check(ctsTime.Token);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return waitData.WaitResult;
|
||||
return waitData.CompletedData;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -599,13 +637,14 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Channel.ChannelReceivedWaitDict.TryRemove(sign, out _);
|
||||
waitLock?.Release();
|
||||
clientChannel.WaitHandlePool.Destroy(sign);
|
||||
waitData?.SafeDispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -964,10 +1003,10 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<string> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<string> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||
if (bitConverter.StringLength == null) return new OperResult(AppResource.StringAddressError);
|
||||
if (bitConverter.StringLength == null) return EasyValueTask.FromResult(new OperResult(AppResource.StringAddressError));
|
||||
List<ReadOnlyMemory<byte>> bytes = new();
|
||||
foreach (var a in value.Span)
|
||||
{
|
||||
@@ -975,7 +1014,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
bytes.Add(data.ArrayExpandToLength(bitConverter.StringLength ?? data.Length));
|
||||
}
|
||||
|
||||
return await WriteAsync(address, bytes.CombineMemoryBlocks(), DataTypeEnum.String, cancellationToken).ConfigureAwait(false);
|
||||
return WriteAsync(address, bytes.CombineMemoryBlocks(), DataTypeEnum.String, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion 写入数组
|
||||
@@ -997,7 +1036,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
{
|
||||
if (Channel is ITcpServiceChannel tcpServiceChannel)
|
||||
{
|
||||
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool.SafeDispose());
|
||||
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool?.CancelAll());
|
||||
}
|
||||
|
||||
try
|
||||
@@ -1006,7 +1045,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
_ = Channel.CloseAsync();
|
||||
if (Channel is IClientChannel client)
|
||||
{
|
||||
client.WaitHandlePool.SafeDispose();
|
||||
client.WaitHandlePool?.CancelAll();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1020,7 +1059,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
{
|
||||
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
|
||||
{
|
||||
client.WaitHandlePool?.SafeDispose();
|
||||
client.WaitHandlePool?.CancelAll();
|
||||
_ = client.CloseAsync();
|
||||
}
|
||||
}
|
||||
@@ -1049,7 +1088,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
{
|
||||
if (Channel is ITcpServiceChannel tcpServiceChannel)
|
||||
{
|
||||
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool.SafeDispose());
|
||||
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool?.CancelAll());
|
||||
}
|
||||
|
||||
try
|
||||
@@ -1058,7 +1097,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
await Channel.CloseAsync().ConfigureAwait(false);
|
||||
if (Channel is IClientChannel client)
|
||||
{
|
||||
client.WaitHandlePool.SafeDispose();
|
||||
client.WaitHandlePool?.CancelAll();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1072,7 +1111,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
{
|
||||
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
|
||||
{
|
||||
client.WaitHandlePool?.SafeDispose();
|
||||
client.WaitHandlePool?.CancelAll();
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
@@ -132,24 +132,43 @@ public static partial class DeviceExtension
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当状态不是<see cref="WaitDataStatus.SetRunning"/>时返回异常。
|
||||
/// 当状态不是<see cref="WaitDataStatus.Success"/>时返回异常。
|
||||
/// </summary>
|
||||
public static OperResult Check(this WaitDataAsync<MessageBase> waitDataAsync)
|
||||
public static OperResult Check(this AsyncWaitData<MessageBase> waitDataAsync, CancellationToken cancellationToken)
|
||||
{
|
||||
switch (waitDataAsync.Status)
|
||||
{
|
||||
case WaitDataStatus.SetRunning:
|
||||
case WaitDataStatus.Success:
|
||||
return new();
|
||||
|
||||
case WaitDataStatus.Canceled: return new(new OperationCanceledException());
|
||||
case WaitDataStatus.Canceled:
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (waitDataAsync.CompletedData != null)
|
||||
{
|
||||
waitDataAsync.CompletedData.Exception = new TimeoutException();
|
||||
if (waitDataAsync.CompletedData.IsSuccess) waitDataAsync.CompletedData.OperCode = 999;
|
||||
if (waitDataAsync.CompletedData.ErrorMessage.IsNullOrEmpty()) waitDataAsync.CompletedData.ErrorMessage = "Timeout";
|
||||
return new(waitDataAsync.CompletedData);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(new TimeoutException());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return new(new OperationCanceledException());
|
||||
}
|
||||
case WaitDataStatus.Overtime:
|
||||
{
|
||||
if (waitDataAsync.WaitResult != null)
|
||||
if (waitDataAsync.CompletedData != null)
|
||||
{
|
||||
waitDataAsync.WaitResult.Exception = new TimeoutException();
|
||||
if (waitDataAsync.WaitResult.IsSuccess) waitDataAsync.WaitResult.OperCode = 999;
|
||||
if (waitDataAsync.WaitResult.ErrorMessage.IsNullOrEmpty()) waitDataAsync.WaitResult.ErrorMessage = "Timeout";
|
||||
return new(waitDataAsync.WaitResult);
|
||||
waitDataAsync.CompletedData.Exception = new TimeoutException();
|
||||
if (waitDataAsync.CompletedData.IsSuccess) waitDataAsync.CompletedData.OperCode = 999;
|
||||
if (waitDataAsync.CompletedData.ErrorMessage.IsNullOrEmpty()) waitDataAsync.CompletedData.ErrorMessage = "Timeout";
|
||||
return new(waitDataAsync.CompletedData);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -160,7 +179,7 @@ public static partial class DeviceExtension
|
||||
case WaitDataStatus.Default:
|
||||
default:
|
||||
{
|
||||
return waitDataAsync.WaitResult == null ? new(new Exception(AppResource.UnknownError)) : new(waitDataAsync.WaitResult);
|
||||
return waitDataAsync.CompletedData == null ? new(new Exception(AppResource.UnknownError)) : new(waitDataAsync.CompletedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -20,70 +21,8 @@ namespace ThingsGateway.Foundation;
|
||||
/// </summary>
|
||||
public static class ByteBlockExtension
|
||||
{
|
||||
public static void WriteBackAddValue<TWriter>(ref TWriter writer, byte value, int pos)
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
int nowPos = writer.Position;
|
||||
writer.Position = pos;
|
||||
WriterExtension.WriteValue(ref writer, (byte)(writer.Span[pos] + value));
|
||||
writer.Position = nowPos;
|
||||
}
|
||||
|
||||
|
||||
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, int pos)
|
||||
where T : unmanaged
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
int nowPos = writer.Position;
|
||||
writer.Position = pos;
|
||||
WriterExtension.WriteValue(ref writer, value);
|
||||
writer.Position = nowPos;
|
||||
}
|
||||
|
||||
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, int pos)
|
||||
where T : unmanaged
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
int nowPos = writer.Position;
|
||||
writer.Position = pos;
|
||||
WriterExtension.WriteValue(ref writer, value, endianType);
|
||||
writer.Position = nowPos;
|
||||
}
|
||||
|
||||
public static void WriteBackNormalString<TWriter>(ref TWriter writer, string value, Encoding encoding, int pos)
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
|
||||
int nowPos = writer.Position;
|
||||
writer.Position = pos;
|
||||
WriterExtension.WriteNormalString(ref writer, value, encoding);
|
||||
writer.Position = nowPos;
|
||||
}
|
||||
|
||||
|
||||
public static string ReadNormalString<TReader>(ref TReader reader, int length)
|
||||
where TReader : IBytesReader
|
||||
{
|
||||
var span = reader.GetSpan(length).Slice(0, length);
|
||||
var str = span.ToString(Encoding.UTF8);
|
||||
reader.Advance(length);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将值类型的字节块转换为普通的字节块。
|
||||
/// </summary>
|
||||
/// <param name="valueByteBlock">要转换的值类型字节块。</param>
|
||||
/// <returns>一个新的字节块对象。</returns>
|
||||
public static ByteBlock AsByteBlock(this ValueByteBlock valueByteBlock)
|
||||
{
|
||||
ByteBlock byteBlock = new ByteBlock(valueByteBlock.TotalMemory.Slice(0, valueByteBlock.Length));
|
||||
byteBlock.Position = valueByteBlock.Position;
|
||||
byteBlock.SetLength(valueByteBlock.Length);
|
||||
return byteBlock;
|
||||
}
|
||||
|
||||
#region ToArray
|
||||
|
||||
/// <summary>
|
||||
@@ -259,5 +198,252 @@ public static class ByteBlockExtension
|
||||
return ToString(byteBlock, offset, byteBlock.BytesRead + byteBlock.BytesRemaining - offset);
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public static string ToString<TByteBlock>(this TByteBlock byteBlock) where TByteBlock : IBytesReader
|
||||
{
|
||||
return byteBlock.TotalSequence.ToString(Encoding.UTF8);
|
||||
}
|
||||
#endregion ToString
|
||||
|
||||
#region ToHexString
|
||||
|
||||
|
||||
public static string ToHexString<TByteBlock>(this TByteBlock byteBlock, long offset, long length, char splite = default) where TByteBlock : IBytesReader
|
||||
{
|
||||
return byteBlock.TotalSequence.Slice(offset, length).ToHexString(Encoding.UTF8, splite);
|
||||
}
|
||||
public static string ToHexString(this ReadOnlySequence<byte> byteBlock, Encoding encoding, char splite = default)
|
||||
{
|
||||
return byteBlock.ToHexString(splite);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static string ToHexString<TByteBlock>(this TByteBlock byteBlock, long offset, char splite = default) where TByteBlock : IBytesReader
|
||||
{
|
||||
return ToHexString(byteBlock, offset, byteBlock.BytesRead + byteBlock.BytesRemaining - offset, splite);
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public static string ToHexString<TByteBlock>(this TByteBlock byteBlock, char splite = default) where TByteBlock : IBytesReader
|
||||
{
|
||||
return byteBlock.TotalSequence.ToHexString(Encoding.UTF8, splite);
|
||||
|
||||
}
|
||||
|
||||
#endregion ToHexString
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 在 <see cref="ReadOnlySequence{T}"/> 中查找第一个与指定 <see cref="byte"/> 匹配的子序列的起始索引。
|
||||
/// <para>如果未找到则返回 -1。</para>
|
||||
/// </summary>
|
||||
/// <param name="sequence">要搜索的字节序列。</param>
|
||||
/// <param name="firstByte">要查找的字节。</param>
|
||||
/// <returns>匹配子序列的起始索引,未找到则返回 -1。</returns>
|
||||
public static long IndexOf(this ReadOnlySequence<byte> sequence, byte firstByte)
|
||||
{
|
||||
|
||||
if (sequence.Length < 1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
long globalPosition = 0;
|
||||
var enumerator = sequence.GetEnumerator();
|
||||
|
||||
// 遍历每个内存段
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var currentSpan = enumerator.Current.Span;
|
||||
var localIndex = 0;
|
||||
|
||||
// 在当前段中搜索首字节
|
||||
while (localIndex < currentSpan.Length)
|
||||
{
|
||||
// 查找首字节匹配位置
|
||||
var matchIndex = currentSpan.Slice(localIndex).IndexOf(firstByte);
|
||||
if (matchIndex == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
localIndex += matchIndex;
|
||||
var globalIndex = globalPosition + localIndex;
|
||||
|
||||
// 检查剩余长度是否足够
|
||||
if (sequence.Length - globalIndex < 1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 检查完整匹配
|
||||
if (IsMatch(sequence, globalIndex, firstByte))
|
||||
{
|
||||
return globalIndex;
|
||||
}
|
||||
|
||||
localIndex++; // 继续搜索下一个位置
|
||||
}
|
||||
globalPosition += currentSpan.Length;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static bool IsMatch(ReadOnlySequence<byte> sequence, long start, byte value)
|
||||
{
|
||||
return sequence.Slice(start, 1).First.Span[0] == value;
|
||||
}
|
||||
|
||||
|
||||
public static byte GetByte(this ReadOnlySequence<byte> sequence, long index)
|
||||
{
|
||||
return sequence.Slice(index).First.Span[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算指定区间字节的和
|
||||
/// </summary>
|
||||
public static long SumRange(this ReadOnlySequence<byte> sequence, long start, long count)
|
||||
{
|
||||
if (start < 0 || count < 0 || start + count > sequence.Length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
long sum = 0;
|
||||
long remaining = count;
|
||||
|
||||
foreach (var segment in sequence)
|
||||
{
|
||||
if (start >= segment.Length)
|
||||
{
|
||||
// 起点不在当前段
|
||||
start -= segment.Length;
|
||||
continue;
|
||||
}
|
||||
|
||||
var span = segment.Span;
|
||||
int take = (int)Math.Min(segment.Length - start, remaining);
|
||||
|
||||
for (int i = 0; i < take; i++)
|
||||
sum += span[(int)start + i];
|
||||
|
||||
remaining -= take;
|
||||
start = 0; // 后续段从头开始
|
||||
|
||||
if (remaining == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 比较 ReadOnlySequence 与 ReadOnlySpan 的内容是否一致。
|
||||
/// </summary>
|
||||
public static bool SequenceEqual<T>(this ReadOnlySequence<T> sequence, ReadOnlySpan<T> other)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
if (sequence.Length != other.Length)
|
||||
return false;
|
||||
|
||||
// 单段,直接比较
|
||||
if (sequence.IsSingleSegment)
|
||||
{
|
||||
return sequence.First.Span.SequenceEqual(other);
|
||||
}
|
||||
|
||||
// 多段,逐段比较
|
||||
int offset = 0;
|
||||
foreach (var segment in sequence)
|
||||
{
|
||||
var span = segment.Span;
|
||||
if (!other.Slice(offset, span.Length).SequenceEqual(span))
|
||||
return false;
|
||||
offset += span.Length;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的字节块从当前位置<see cref="IByteBlockCore.Position"/>转换为【新】字节数组,指定长度。
|
||||
/// </summary>
|
||||
/// <typeparam name="TByteBlock">实现<see cref="IByteBlock"/>接口的字节块类型。</typeparam>
|
||||
/// <param name="byteBlock">字节块对象。</param>
|
||||
/// <param name="length">要转换为数组的长度。</param>
|
||||
/// <returns>从当前位置开始,指定长度的【新】字节数组。</returns>
|
||||
public static byte[] ToArrayTake<TByteBlock>(this TByteBlock byteBlock, long length) where TByteBlock : IBytesReader
|
||||
{
|
||||
return byteBlock.Sequence.Slice(0, length).ToArray();
|
||||
}
|
||||
public static void Write<TByteBlock>(ref TByteBlock byteBlock, ReadOnlySequence<byte> bytes) where TByteBlock : IBytesWriter
|
||||
{
|
||||
foreach (var item in bytes)
|
||||
{
|
||||
byteBlock.Write(item.Span);
|
||||
}
|
||||
}
|
||||
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, int pos)
|
||||
where T : unmanaged
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
var nowPos = (int)writer.WrittenCount - pos;
|
||||
writer.Advance(-nowPos);
|
||||
var size = Unsafe.SizeOf<T>();
|
||||
var span = writer.GetSpan(size);
|
||||
TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value);
|
||||
writer.Advance(nowPos);
|
||||
|
||||
}
|
||||
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, long pos)
|
||||
where T : unmanaged
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
var nowPos = (int)(writer.WrittenCount - pos);
|
||||
writer.Advance(-nowPos);
|
||||
var size = Unsafe.SizeOf<T>();
|
||||
var span = writer.GetSpan(size);
|
||||
TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value);
|
||||
writer.Advance(nowPos);
|
||||
|
||||
}
|
||||
|
||||
public static string ReadNormalString<TReader>(ref TReader reader, int length)
|
||||
where TReader : IBytesReader
|
||||
{
|
||||
var span = reader.GetSpan(length).Slice(0, length);
|
||||
var str = span.ToString(Encoding.UTF8);
|
||||
reader.Advance(length);
|
||||
return str;
|
||||
}
|
||||
|
||||
public static void WriteBackNormalString<TWriter>(ref TWriter writer, string value, Encoding encoding, int pos)
|
||||
where TWriter : IByteBlockWriter
|
||||
{
|
||||
var nowPos = (int)(writer.WrittenCount - pos);
|
||||
writer.Advance(-nowPos);
|
||||
WriterExtension.WriteNormalString(ref writer, value, encoding);
|
||||
writer.Advance(nowPos);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static int WriteNormalString(this Span<byte> span, string value, Encoding encoding)
|
||||
{
|
||||
var maxSize = encoding.GetMaxByteCount(value.Length);
|
||||
var chars = value.AsSpan();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (char* p = &chars[0])
|
||||
{
|
||||
fixed (byte* p1 = &span[0])
|
||||
{
|
||||
var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -47,6 +48,14 @@ public static class ByteExtensions
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
public static byte[] BoolToByte(this Span<bool> value, byte trueData = 0xff)
|
||||
{
|
||||
return BoolToByte((ReadOnlySpan<bool>)value, trueData); ;
|
||||
}
|
||||
public static byte[] BoolToByte(this bool[] value, byte trueData = 0xff)
|
||||
{
|
||||
return BoolToByte((ReadOnlySpan<bool>)value, trueData); ;
|
||||
}
|
||||
public static bool[] ByteToBool(this ReadOnlySpan<byte> value)
|
||||
{
|
||||
bool[] bytes = new bool[value.Length];
|
||||
@@ -76,6 +85,7 @@ public static class ByteExtensions
|
||||
{
|
||||
return BytesAdd((ReadOnlySpan<byte>)bytes, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数组内容分别相加某个数字
|
||||
/// </summary>
|
||||
@@ -94,6 +104,31 @@ public static class ByteExtensions
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将 ReadOnlySequence 的每个字节加上指定值,返回新的 byte 数组。
|
||||
/// </summary>
|
||||
public static byte[] BytesAdd(this ReadOnlySequence<byte> sequence, int value)
|
||||
{
|
||||
if (sequence.Length == 0)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
byte[] result = new byte[sequence.Length];
|
||||
int offset = 0;
|
||||
|
||||
foreach (var segment in sequence)
|
||||
{
|
||||
var span = segment.Span;
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
result[offset + i] = (byte)(span[i] + value);
|
||||
}
|
||||
offset += span.Length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将byte数组按照双字节进行反转,如果为单数的情况,则自动补齐<br />
|
||||
/// </summary>
|
||||
@@ -110,7 +145,6 @@ public static class ByteExtensions
|
||||
|
||||
// 如果是奇数,自动补齐 0
|
||||
int evenLen = (len % 2 == 0) ? len : len + 1;
|
||||
if (evenLen == len) return inBytes;
|
||||
|
||||
Span<byte> result = new byte[evenLen];
|
||||
inBytes.CopyTo(result);
|
||||
@@ -142,7 +176,6 @@ public static class ByteExtensions
|
||||
|
||||
// 如果是奇数,自动补齐 0
|
||||
int evenLen = (len % 2 == 0) ? len : len + 1;
|
||||
if (evenLen == len) return inBytes;
|
||||
|
||||
byte[] result = new byte[evenLen];
|
||||
inBytes.CopyTo(result);
|
||||
@@ -187,6 +220,34 @@ public static class ByteExtensions
|
||||
return boolArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 <see cref="ReadOnlySequence{T}"/> 中提取位数组,length 代表位数
|
||||
/// </summary>
|
||||
/// <param name="sequence">原始字节序列</param>
|
||||
/// <param name="length">想要转换的位数,如果超出字节序列长度 * 8,则自动缩小为最大位数</param>
|
||||
/// <returns>转换后的布尔数组</returns>
|
||||
public static bool[] ByteToBoolArray(this ReadOnlySequence<byte> sequence, int length)
|
||||
{
|
||||
// 计算字节序列能提供的最大位数
|
||||
long maxBitLength = sequence.Length * 8;
|
||||
if (length > maxBitLength)
|
||||
length = (int)maxBitLength;
|
||||
|
||||
bool[] boolArray = new bool[length];
|
||||
|
||||
int bitIndex = 0; // 目标位索引
|
||||
|
||||
foreach (var segment in sequence)
|
||||
{
|
||||
var span = segment.Span;
|
||||
for (int i = 0; i < span.Length && bitIndex < length; i++)
|
||||
{
|
||||
boolArray[bitIndex] = span[i / 8].BoolOnByteIndex(i % 8);
|
||||
}
|
||||
}
|
||||
|
||||
return boolArray;
|
||||
}
|
||||
|
||||
public static ReadOnlyMemory<byte> CombineMemoryBlocks(this List<ReadOnlyMemory<byte>> blocks)
|
||||
{
|
||||
@@ -281,4 +342,15 @@ public static class ByteExtensions
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this ReadOnlySequence<byte> buffer, char splite = default, int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
|
||||
namespace ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -245,6 +247,29 @@ public static class GenericExtensions
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<ReadOnlySequence<T>> ChunkBetter<T>(this ReadOnlySequence<T> sequence, int groupSize)
|
||||
{
|
||||
if (groupSize <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(groupSize));
|
||||
|
||||
var start = sequence.Start;
|
||||
var remaining = sequence.Length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var len = (int)Math.Min(groupSize, remaining);
|
||||
|
||||
// 计算 chunk 的 end 位置
|
||||
var end = sequence.GetPosition(len, start);
|
||||
|
||||
yield return sequence.Slice(start, end);
|
||||
|
||||
// 移动起始位置
|
||||
start = end;
|
||||
remaining -= len;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>拷贝当前的实例数组,是基于引用层的浅拷贝,如果类型为值类型,那就是深度拷贝,如果类型为引用类型,就是浅拷贝</summary>
|
||||
public static T[] CopyArray<T>(this T[] value)
|
||||
{
|
||||
|
@@ -51,6 +51,7 @@ public class LogDataCache
|
||||
/// <summary>高性能日志文件读取器(支持倒序读取)</summary>
|
||||
public static class TextFileReader
|
||||
{
|
||||
|
||||
private static readonly MemoryCache _cache = new() { Expire = 30 };
|
||||
private static readonly MemoryCache _fileLocks = new();
|
||||
private static readonly ArrayPool<byte> _bytePool = ArrayPool<byte>.Shared;
|
||||
@@ -59,7 +60,7 @@ public static class TextFileReader
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">目录路径</param>
|
||||
/// <returns>包含文件信息的列表</returns>
|
||||
public static OperResult<List<string>> GetLogFilesAsync(string directoryPath)
|
||||
public static OperResult<List<string>> GetLogFiles(string directoryPath)
|
||||
{
|
||||
OperResult<List<string>> result = new(); // 初始化结果对象
|
||||
// 检查目录是否存在
|
||||
@@ -81,17 +82,25 @@ public static class TextFileReader
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// 获取文件信息并按照最后写入时间降序排序
|
||||
var fileInfos = files.Select(filePath => new FileInfo(filePath))
|
||||
.OrderByDescending(x => x.LastWriteTime)
|
||||
.Select(x => x.FullName)
|
||||
.Select(x =>
|
||||
{
|
||||
#if !NET6_0_OR_GREATER
|
||||
return PathHelper.GetRelativePath(AppContext.BaseDirectory, x.FullName);
|
||||
#else
|
||||
return Path.GetRelativePath(AppContext.BaseDirectory, x.FullName);
|
||||
#endif
|
||||
})
|
||||
.ToList();
|
||||
result.OperCode = 0;
|
||||
result.Content = fileInfos;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static OperResult<List<LogData>> LastLogDataAsync(string file, int lineCount = 200)
|
||||
public static OperResult<List<LogData>> LastLogData(string file, int lineCount = 200)
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
return new OperResult<List<LogData>>("The file path is invalid");
|
||||
@@ -104,7 +113,7 @@ public static class TextFileReader
|
||||
{
|
||||
var fileInfo = new FileInfo(file);
|
||||
var length = fileInfo.Length;
|
||||
var cacheKey = $"{nameof(TextFileReader)}_{nameof(LastLogDataAsync)}_{file})";
|
||||
var cacheKey = $"{nameof(TextFileReader)}_{nameof(LastLogData)}_{file})";
|
||||
if (_cache.TryGetValue<LogDataCache>(cacheKey, out var cachedData))
|
||||
{
|
||||
if (cachedData != null && cachedData.Length == length)
|
||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Foundation;
|
||||
|
||||
public class TextFileReadService : ITextFileReadService
|
||||
{
|
||||
public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath) => Task.FromResult(TextFileReader.GetLogFilesAsync(directoryPath));
|
||||
public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath) => Task.FromResult(TextFileReader.GetLogFiles(directoryPath));
|
||||
|
||||
public Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200) => Task.FromResult(TextFileReader.LastLogDataAsync(file, lineCount));
|
||||
public Task<OperResult<List<LogData>>> LastLogDataAsync(string file, int lineCount = 200) => Task.FromResult(TextFileReader.LastLogData(file, lineCount));
|
||||
}
|
||||
|
@@ -11,8 +11,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" />
|
||||
<PackageReference Include="TouchSocket" Version="4.0.0-Alpha.12" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-Alpha.12" />
|
||||
<PackageReference Include="TouchSocket" Version="4.0.0-beta.6" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
@@ -70,4 +72,64 @@ public static class CRC16Utils
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通过指定多项式码来获取对应的数据的CRC校验码
|
||||
/// </summary>
|
||||
/// <param name="sequence">需要校验的数据,不包含CRC字节</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] Crc16Only(ReadOnlySequence<byte> sequence)
|
||||
{
|
||||
return Crc16Only(sequence, 0xA001);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过指定多项式码来获取对应的数据的CRC校验码
|
||||
/// </summary>
|
||||
/// <param name="sequence">需要校验的数据,不包含CRC字节</param>
|
||||
/// <param name="xdapoly">多项式</param>
|
||||
/// <param name="crc16">是否低位在前(true=低字节优先,false=高字节优先)</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] Crc16Only(ReadOnlySequence<byte> sequence, int xdapoly, bool crc16 = true)
|
||||
{
|
||||
int num = 0xFFFF;
|
||||
|
||||
foreach (var segment in sequence)
|
||||
{
|
||||
var span = segment.Span;
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
num = (num >> (crc16 ? 0 : 8)) ^ span[i];
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int num2 = num & 1;
|
||||
num >>= 1;
|
||||
if (num2 == 1)
|
||||
{
|
||||
num ^= xdapoly;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (crc16)
|
||||
{
|
||||
return new byte[]
|
||||
{
|
||||
(byte)(num & 0xFFu),
|
||||
(byte)(num >> 8)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new byte[]
|
||||
{
|
||||
(byte)(num >> 8),
|
||||
(byte)(num & 0xFFu)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -59,6 +60,53 @@ public static class DataTransUtil
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ByteToHexString(ReadOnlySequence<byte> InBytes, char segment = default, int newLineCount = 0) => ByteToHexString(InBytes, 0, InBytes.Length, segment, newLineCount);
|
||||
|
||||
public static string ByteToHexString(ReadOnlySequence<byte> sequence, long offset, long length, char segment = default, int newLineCount = 0)
|
||||
{
|
||||
if (length <= 0 || offset < 0 || sequence.Length < offset + length)
|
||||
return string.Empty;
|
||||
|
||||
var estimatedSize = length * (segment != default ? 3 : 2) + (length / Math.Max(newLineCount, int.MaxValue));
|
||||
StringBuilder sb = new StringBuilder((int)estimatedSize);
|
||||
|
||||
int totalConsumed = 0;
|
||||
int totalWritten = 0;
|
||||
|
||||
foreach (var memory in sequence)
|
||||
{
|
||||
var span = memory.Span;
|
||||
|
||||
for (int i = 0; i < span.Length && totalWritten < length; i++)
|
||||
{
|
||||
if (totalConsumed >= offset)
|
||||
{
|
||||
sb.Append(span[i].ToString("X2"));
|
||||
|
||||
if (totalWritten < length - 1)
|
||||
{
|
||||
if (segment != default)
|
||||
sb.Append(segment);
|
||||
|
||||
if (newLineCount > 0 && (totalWritten + 1) % newLineCount == 0)
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
totalWritten++;
|
||||
}
|
||||
|
||||
totalConsumed++;
|
||||
if (totalConsumed >= offset + length)
|
||||
break;
|
||||
}
|
||||
|
||||
if (totalWritten >= length)
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取Bcd值
|
||||
|
@@ -28,7 +28,7 @@ public static class JTokenUtil
|
||||
{
|
||||
try
|
||||
{
|
||||
if (item.IsNullOrWhiteSpace())
|
||||
if (item.IsNullOrEmpty())
|
||||
return JValue.CreateNull();
|
||||
|
||||
if (bool.TryParse(item, out bool parseBool))
|
||||
|
@@ -28,7 +28,7 @@ public class AsyncReadWriteLock : IAsyncDisposable
|
||||
/// <summary>
|
||||
/// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。
|
||||
/// </summary>
|
||||
public async Task<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken)
|
||||
public async ValueTask<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
if (Interlocked.Read(ref _writerCount) > 0)
|
||||
@@ -51,7 +51,7 @@ public class AsyncReadWriteLock : IAsyncDisposable
|
||||
/// <summary>
|
||||
/// 获取写锁,阻止所有读取。
|
||||
/// </summary>
|
||||
public async Task<IDisposable> WriterLockAsync(CancellationToken cancellationToken)
|
||||
public async ValueTask<IDisposable> WriterLockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
if (Interlocked.Increment(ref _writerCount) == 1)
|
||||
|
@@ -7,7 +7,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public class CronScheduledTask : DisposeBase, IScheduledTask
|
||||
{
|
||||
private int _interval10MS = 10;
|
||||
private int next = 10;
|
||||
private string _interval;
|
||||
private readonly Func<object?, CancellationToken, Task> _taskFunc;
|
||||
private readonly Func<object?, CancellationToken, ValueTask> _valueTaskFunc;
|
||||
@@ -62,9 +62,9 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
||||
_timer?.Dispose();
|
||||
if (Check()) return;
|
||||
if (_taskAction != null)
|
||||
_timer = new TimerX(TimerCallback, _state, _interval, nameof(IScheduledTask)) { Async = true };
|
||||
_timer = new TimerX(TimerCallback, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
|
||||
else if (_taskFunc != null || _valueTaskFunc != null)
|
||||
_timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(IScheduledTask)) { Async = true };
|
||||
_timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
|
||||
}
|
||||
|
||||
private async ValueTask TimerCallbackAsync(object? state)
|
||||
@@ -107,7 +107,7 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
||||
{
|
||||
if (!Check())
|
||||
{
|
||||
SetNext(_interval10MS);
|
||||
SetNext(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
||||
{
|
||||
if (!Check())
|
||||
{
|
||||
SetNext(_interval10MS);
|
||||
SetNext(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace ThingsGateway.Gateway.Application
|
||||
{
|
||||
public interface IScheduledTask
|
||||
public interface IScheduledTask : IDisposable
|
||||
{
|
||||
bool Change(int dueTime, int period);
|
||||
void SetNext(int interval);
|
||||
|
@@ -7,7 +7,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntIntervalTask
|
||||
{
|
||||
private int _interval10MS = 10;
|
||||
private int next = 10;
|
||||
public int IntervalMS { get; }
|
||||
private readonly Func<object?, CancellationToken, Task> _taskFunc;
|
||||
private readonly Func<object?, CancellationToken, ValueTask> _valueTaskFunc;
|
||||
@@ -48,7 +48,7 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
|
||||
{
|
||||
_timer?.Dispose();
|
||||
if (!Check())
|
||||
_timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(IScheduledTask)) { Async = true };
|
||||
_timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { Async = true, Reentrant = false };
|
||||
}
|
||||
|
||||
private async ValueTask DoAsync(object? state)
|
||||
@@ -70,7 +70,6 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
|
||||
// 减少一个触发次数
|
||||
Interlocked.Decrement(ref _pendingTriggers);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (_taskFunc != null)
|
||||
@@ -92,9 +91,9 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
|
||||
|
||||
if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1)
|
||||
{
|
||||
if (!Check())
|
||||
if (!Check() && IntervalMS > 8)
|
||||
{
|
||||
SetNext(_interval10MS);
|
||||
SetNext(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntIntervalTask
|
||||
{
|
||||
private int _interval10MS = 10;
|
||||
private int next = 10;
|
||||
public int IntervalMS { get; }
|
||||
private readonly Action<object?, CancellationToken> _taskAction;
|
||||
private readonly CancellationToken _token;
|
||||
@@ -40,7 +40,7 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter
|
||||
{
|
||||
_timer?.Dispose();
|
||||
if (!Check())
|
||||
_timer = new TimerX(TimerCallback, _state, IntervalMS, IntervalMS, nameof(IScheduledTask)) { Async = true };
|
||||
_timer = new TimerX(TimerCallback, _state, IntervalMS, IntervalMS, nameof(ScheduledSyncTask)) { Async = true, Reentrant = false };
|
||||
}
|
||||
|
||||
private void TimerCallback(object? state)
|
||||
@@ -78,9 +78,9 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter
|
||||
|
||||
if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1)
|
||||
{
|
||||
if (!Check())
|
||||
if (!Check() && IntervalMS > 8)
|
||||
{
|
||||
SetNext(_interval10MS);
|
||||
SetNext(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
[RequestAudit]
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
[TouchSocket.WebApi.Router("/miniapi/[api]/[action]")]
|
||||
[TouchSocket.WebApi.Router("/miniapi/control/[action]")]
|
||||
[TouchSocket.WebApi.EnableCors("cors")]
|
||||
public class ControlController : ControllerBase, IRpcServer
|
||||
{
|
||||
|
@@ -0,0 +1,716 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using ThingsGateway.Authentication;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Rpc;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
[ApiDescriptionSettings("ThingsGateway.OpenApi", Order = 200)]
|
||||
[Route("openApi/management/[action]")]
|
||||
[RolePermission]
|
||||
[RequestAudit]
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
[TouchSocket.WebApi.Router("/miniapi/management/[action]")]
|
||||
[TouchSocket.WebApi.EnableCors("cors")]
|
||||
public partial class ManagementController : ControllerBase, IRpcServer
|
||||
{
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<BackendLog>> BackendLogPageAsync(QueryPageOptions option) => App.GetService<IBackendLogService>().BackendLogPageAsync(option);
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<List<BackendLogDayStatisticsOutput>> BackendLogStatisticsByDayAsync(int day) => App.GetService<IBackendLogService>().BackendLogStatisticsByDayAsync(day);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> BatchEditChannelAsync([FromBody] BatchEditChannelRequest request) =>
|
||||
App.GetService<IChannelPageService>()
|
||||
.BatchEditChannelAsync(request.Models, request.OldModel, request.Model, request.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> BatchEditDeviceAsync([FromBody] BatchEditDeviceRequest request) =>
|
||||
App.GetService<IDevicePageService>()
|
||||
.BatchEditDeviceAsync(request.Models, request.OldModel, request.Model, request.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> BatchEditVariableAsync([FromBody] BatchEditVariableRequest request) =>
|
||||
App.GetService<IVariablePageService>()
|
||||
.BatchEditVariableAsync(request.Models, request.OldModel, request.Model, request.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> BatchSaveVariableAsync([FromBody] BatchSaveVariableRequest request) =>
|
||||
App.GetService<IVariablePageService>()
|
||||
.BatchSaveVariableAsync(request.Input, request.Type, request.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<LogLevel> ChannelLogLevelAsync(long id) =>
|
||||
App.GetService<IChannelPageService>().ChannelLogLevelAsync(id);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> ClearChannelAsync(bool restart) =>
|
||||
App.GetService<IChannelPageService>().ClearChannelAsync(restart);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> ClearDeviceAsync(bool restart) =>
|
||||
App.GetService<IDevicePageService>().ClearDeviceAsync(restart);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task ClearRulesAsync() => App.GetService<IRulesService>().ClearRulesAsync();
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> ClearVariableAsync(bool restart) =>
|
||||
App.GetService<IVariablePageService>().ClearVariableAsync(restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task CopyChannelAsync([FromBody] CopyChannelRequest request) =>
|
||||
App.GetService<IChannelPageService>()
|
||||
.CopyChannelAsync(request.CopyCount,
|
||||
request.CopyChannelNamePrefix,
|
||||
request.CopyChannelNameSuffixNumber,
|
||||
request.CopyDeviceNamePrefix,
|
||||
request.CopyDeviceNameSuffixNumber,
|
||||
request.ChannelId,
|
||||
request.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task CopyDeviceAsync([FromBody] CopyDeviceRequest request) =>
|
||||
App.GetService<IDevicePageService>()
|
||||
.CopyDeviceAsync(request.CopyCount,
|
||||
request.CopyDeviceNamePrefix,
|
||||
request.CopyDeviceNameSuffixNumber,
|
||||
request.DeviceId,
|
||||
request.AutoRestartThread);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task CopyVariableAsync([FromBody] CopyVariableRequest request) =>
|
||||
App.GetService<IVariablePageService>()
|
||||
.CopyVariableAsync(request.Model,
|
||||
request.CopyCount,
|
||||
request.CopyVariableNamePrefix,
|
||||
request.CopyVariableNameSuffixNumber,
|
||||
request.Restart);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task DeleteBackendLogAsync() => App.GetService<IBackendLogService>().DeleteBackendLogAsync();
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> DeleteChannelAsync([FromBody] DeleteRequest request) =>
|
||||
App.GetService<IChannelPageService>().DeleteChannelAsync(request.Ids, request.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> DeleteDeviceAsync([FromBody] DeleteRequest request) =>
|
||||
App.GetService<IDevicePageService>().DeleteDeviceAsync(request.Ids, request.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> DeleteVariableAsync([FromBody] DeleteRequest request) =>
|
||||
App.GetService<IVariablePageService>().DeleteVariableAsync(request.Ids, request.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task DeleteRpcLogAsync() => App.GetService<IRpcLogService>().DeleteRpcLogAsync();
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task DeleteRuleRuntimesAsync(List<long> ids) => App.GetService<IRulesEngineHostedService>().DeleteRuleRuntimesAsync(ids);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> DeleteRulesAsync(List<long> ids) => App.GetService<IRulesService>().DeleteRulesAsync(ids);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<LogLevel> DeviceLogLevelAsync(long id) =>
|
||||
App.GetService<IDevicePageService>().DeviceLogLevelAsync(id);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task DeviceRedundantThreadAsync(long id) =>
|
||||
App.GetService<IDevicePageService>().DeviceRedundantThreadAsync(id);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task EditRedundancyOptionAsync(RedundancyOptions input) => App.GetService<IRedundancyService>().EditRedundancyOptionAsync(input);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task EditRuleRuntimesAsync(Rules rules) => App.GetService<IRulesEngineHostedService>().EditRuleRuntimesAsync(rules);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<USheetDatas> ExportChannelAsync(List<Channel> channels) =>
|
||||
App.GetService<IChannelPageService>().ExportChannelAsync(channels);
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<string> ExportChannelFileAsync(GatewayExportFilter exportFilter) =>
|
||||
App.GetService<IChannelPageService>().ExportChannelFileAsync(exportFilter);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<USheetDatas> ExportDeviceAsync(List<Device> devices) =>
|
||||
App.GetService<IDevicePageService>().ExportDeviceAsync(devices);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<string> ExportDeviceFileAsync(GatewayExportFilter exportFilter) =>
|
||||
App.GetService<IDevicePageService>().ExportDeviceFileAsync(exportFilter);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<USheetDatas> ExportVariableAsync(List<Variable> models, string? sortName, SortOrder sortOrder) =>
|
||||
App.GetService<IVariablePageService>().ExportVariableAsync(models, sortName, sortOrder);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<string> ExportVariableFileAsync(GatewayExportFilter exportFilter) => App.GetService<IVariablePageService>().ExportVariableFileAsync(exportFilter);
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<List<Channel>> GetChannelListAsync([FromBody] GetListRequest<QueryPageOptions> request) =>
|
||||
App.GetService<IChannelPageService>().GetChannelListAsync(request.Options, request.Max);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<List<Device>> GetDeviceListAsync([FromBody] GetListRequest<QueryPageOptions> request) =>
|
||||
App.GetService<IDevicePageService>().GetDeviceListAsync(request.Options, request.Max);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<List<Variable>> GetVariableListAsync([FromBody] GetListRequest<QueryPageOptions> request) =>
|
||||
App.GetService<IVariablePageService>().GetVariableListAsync(request.Options, request.Max);
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<List<Rules>> GetAllRulesAsync() => App.GetService<IRulesService>().GetAllRulesAsync();
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<string> GetChannelNameAsync(long id) =>
|
||||
App.GetService<IChannelPageService>().GetChannelNameAsync(id);
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<IEnumerable<SelectedItem>> GetCurrentUserDeviceSelectedItemsAsync(string searchText, int startIndex, int count) => App.GetService<IGlobalDataService>().GetCurrentUserDeviceSelectedItemsAsync(searchText, startIndex, count);
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<QueryData<SelectedItem>> GetCurrentUserDeviceVariableSelectedItemsAsync(string deviceText, string searchText, int startIndex, int count) => App.GetService<IGlobalDataService>().GetCurrentUserDeviceVariableSelectedItemsAsync(deviceText, searchText, startIndex, count);
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync() => App.GetService<IRealAlarmService>().GetCurrentUserRealAlarmVariablesAsync();
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<Dictionary<long, Tuple<string, string>>> GetDeviceIdNamesAsync() => App.GetService<IDevicePageService>().GetDeviceIdNamesAsync();
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<string> GetDeviceNameAsync(long redundantDeviceId) =>
|
||||
App.GetService<IDevicePageService>().GetDeviceNameAsync(redundantDeviceId);
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<string> GetDevicePluginNameAsync(long id) =>
|
||||
App.GetService<IDevicePageService>().GetDevicePluginNameAsync(id);
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<OperResult<List<string>>> GetLogFilesAsync(string directoryPath) => App.GetService<ITextFileReadService>().GetLogFilesAsync(directoryPath);
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<List<BackendLog>> GetNewBackendLogAsync() => App.GetService<IBackendLogService>().GetNewBackendLogAsync();
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<List<RpcLog>> GetNewRpcLogAsync() => App.GetService<IRpcLogService>().GetNewRpcLogAsync();
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<string> GetPluginNameAsync(long channelId) => App.GetService<IChannelPageService>().GetPluginNameAsync(channelId);
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<List<PluginInfo>> GetPluginsAsync(PluginTypeEnum? pluginType = null) => App.GetService<IPluginPageService>().GetPluginsAsync(pluginType);
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<RedundancyOptions> GetRedundancyAsync() => App.GetService<IRedundancyService>().GetRedundancyAsync();
|
||||
[HttpGet]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Get)]
|
||||
public Task<Rules> GetRuleRuntimesAsync(long rulesId) => App.GetService<IRulesEngineHostedService>().GetRuleRuntimesAsync(rulesId);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task ImportChannelDataAsync([FromBody] ImportChannelInput input) =>
|
||||
App.GetService<IChannelPageService>().ImportChannelAsync(input.UpData, input.InsertData, input.Restart);
|
||||
|
||||
[HttpPost]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync([FromForm] ImportRequest request)
|
||||
{
|
||||
return (await App.GetService<IChannelRuntimeService>().ImportChannelAsync(request.File, request.Restart).ConfigureAwait(false)).AdaptImportPreviewOutputBases();
|
||||
|
||||
}
|
||||
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
[TouchSocket.WebApi.Router("/miniapi/management/ImportChannel")]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> TSImportChannelAsync(TouchSocket.WebApi.IWebApiCallContext callContext)
|
||||
{
|
||||
var path = await callContext.HttpContext.Request.StorageLocalExcel().ConfigureAwait(false);
|
||||
|
||||
return (await GlobalData.ChannelRuntimeService.ImportChannelFileAsync(path, true).ConfigureAwait(false)).AdaptImportPreviewOutputBases(); ;
|
||||
|
||||
}
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
[TouchSocket.WebApi.Router("/miniapi/management/ImportDevice")]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> TSImportDeviceAsync(TouchSocket.WebApi.IWebApiCallContext callContext)
|
||||
{
|
||||
var path = await callContext.HttpContext.Request.StorageLocalExcel().ConfigureAwait(false);
|
||||
|
||||
return (await GlobalData.DeviceRuntimeService.ImportDeviceFileAsync(path, true).ConfigureAwait(false)).AdaptImportPreviewOutputBases(); ;
|
||||
|
||||
}
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
[TouchSocket.WebApi.Router("/miniapi/management/ImportVariable")]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> TSImportVariableAsync(TouchSocket.WebApi.IWebApiCallContext callContext)
|
||||
{
|
||||
var path = await callContext.HttpContext.Request.StorageLocalExcel().ConfigureAwait(false);
|
||||
|
||||
return (await GlobalData.VariableRuntimeService.ImportVariableFileAsync(path, true).ConfigureAwait(false)).AdaptImportPreviewOutputBases();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelUSheetDatasAsync([FromBody] ImportUSheetInput input)
|
||||
{
|
||||
return (await App.GetService<IChannelPageService>().ImportChannelUSheetDatasAsync(input.Input, input.Restart).ConfigureAwait(false)).AdaptImportPreviewOutputBases();
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceAsync([FromForm] ImportRequest request)
|
||||
{
|
||||
|
||||
return (await App.GetService<IDeviceRuntimeService>().ImportDeviceAsync(request.File, request.Restart).ConfigureAwait(false)).AdaptImportPreviewOutputBases();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportDeviceUSheetDatasAsync([FromBody] ImportUSheetInput input)
|
||||
{
|
||||
|
||||
return (await App.GetService<IDevicePageService>().ImportDeviceUSheetDatasAsync(input.Input, input.Restart).ConfigureAwait(false)).AdaptImportPreviewOutputBases();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableAsync([FromForm] ImportRequest request)
|
||||
{
|
||||
return (await App.GetService<IVariableRuntimeService>().ImportVariableAsync(request.File, request.Restart).ConfigureAwait(false)).AdaptImportPreviewOutputBases();
|
||||
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableUSheetDatasAsync([FromBody] ImportUSheetInput input)
|
||||
{
|
||||
return (await App.GetService<IVariablePageService>().ImportVariableUSheetDatasAsync(input.Input, input.Restart).ConfigureAwait(false)).AdaptImportPreviewOutputBases();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task InsertTestDataAsync([FromBody] InsertTestDataInput input) =>
|
||||
App.GetService<IVariablePageService>().InsertTestDataAsync(input.TestVariableCount, input.TestDeviceCount, input.SlaveUrl, input.BusinessEnable, input.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> IsRedundantDeviceAsync(long id) =>
|
||||
App.GetService<IDevicePageService>().IsRedundantDeviceAsync(id);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<OperResult<List<LogData>>> LastLogDataAsync([FromBody] LastLogDataInput input) =>
|
||||
App.GetService<ITextFileReadService>().LastLogDataAsync(input.File, input.LineCount);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<ChannelRuntime>> OnChannelQueryAsync([FromBody] QueryPageOptions options) =>
|
||||
App.GetService<IChannelPageService>().OnChannelQueryAsync(options);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync([FromBody] VirtualizeQueryOption option) =>
|
||||
App.GetService<IChannelPageService>().OnChannelSelectedItemQueryAsync(option);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<DeviceRuntime>> OnDeviceQueryAsync([FromBody] QueryPageOptions options) =>
|
||||
App.GetService<IDevicePageService>().OnDeviceQueryAsync(options);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<SelectedItem>> OnDeviceSelectedItemQueryAsync([FromBody] OnDeviceSelectedItemQueryInput input) =>
|
||||
App.GetService<IDevicePageService>().OnDeviceSelectedItemQueryAsync(input.Option, input.IsCollect);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<SelectedItem>> OnRedundantDevicesQueryAsync([FromBody] OnRedundantDevicesQueryInput input) =>
|
||||
App.GetService<IDevicePageService>().OnRedundantDevicesQueryAsync(input.Option, input.DeviceId, input.ChannelId);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<VariableRuntime>> OnVariableQueryAsync([FromBody] QueryPageOptions options) =>
|
||||
App.GetService<IVariablePageService>().OnVariableQueryAsync(options);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<OperResult<object>> OnWriteVariableAsync(long id, string writeData) =>
|
||||
App.GetService<IVariablePageService>().OnWriteVariableAsync(id, writeData);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task PauseThreadAsync(long id) =>
|
||||
App.GetService<IDevicePageService>().PauseThreadAsync(id);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<PluginInfo>> PluginPageAsync([FromBody] PluginQueryPageOptions options) =>
|
||||
App.GetService<IPluginPageService>().PluginPageAsync(options.Options, options.PluginType);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task RedundancyForcedSync() => App.GetService<IRedundancyHostedService>().RedundancyForcedSync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<LogLevel> RedundancyLogLevelAsync() => App.GetService<IRedundancyHostedService>().RedundancyLogLevelAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<string> RedundancyLogPathAsync() => App.GetService<IRedundancyHostedService>().RedundancyLogPathAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task ReloadPluginAsync() => App.GetService<IPluginPageService>().ReloadPluginAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task RestartChannelAsync(long channelId) =>
|
||||
App.GetService<IChannelPageService>().RestartChannelAsync(channelId);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task RestartChannelsAsync() =>
|
||||
App.GetService<IChannelPageService>().RestartChannelsAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task RestartDeviceAsync(long id, bool deleteCache) =>
|
||||
App.GetService<IDevicePageService>().RestartDeviceAsync(id, deleteCache);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task RestartServerAsync() => App.GetService<IRestartService>().RestartServerAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<RpcLog>> RpcLogPageAsync([FromBody] QueryPageOptions option) =>
|
||||
App.GetService<IRpcLogService>().RpcLogPageAsync(option);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<List<RpcLogDayStatisticsOutput>> RpcLogStatisticsByDayAsync(int day) =>
|
||||
App.GetService<IRpcLogService>().RpcLogStatisticsByDayAsync(day);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<TouchSocket.Core.LogLevel> RulesLogLevelAsync(long rulesId) =>
|
||||
App.GetService<IRulesEngineHostedService>().RulesLogLevelAsync(rulesId);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<string> RulesLogPathAsync(long rulesId) =>
|
||||
App.GetService<IRulesEngineHostedService>().RulesLogPathAsync(rulesId);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<QueryData<Rules>> RulesPageAsync([FromBody] KVQueryPageOptions option) =>
|
||||
App.GetService<IRulesService>().RulesPageAsync(option.Options, option.FilterKeyValueAction);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> SaveChannelAsync([FromBody] SaveChannelInput input) =>
|
||||
App.GetService<IChannelPageService>().SaveChannelAsync(input.Input, input.Type, input.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> SaveDeviceAsync([FromBody] SaveDeviceInput input) =>
|
||||
App.GetService<IDevicePageService>().SaveDeviceAsync(input.Input, input.Type, input.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task SavePluginByPathAsync([FromBody] PluginAddPathInput plugin) =>
|
||||
App.GetService<IPluginPageService>().SavePluginByPathAsync(plugin);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> SaveRulesAsync([FromBody] Rules input, ItemChangedType type) =>
|
||||
App.GetService<IRulesService>().SaveRulesAsync(input, type);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> SaveVariableAsync([FromBody] SaveVariableInput input) =>
|
||||
App.GetService<IVariablePageService>().SaveVariableAsync(input.Input, input.Type, input.Restart);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task SetChannelLogLevelAsync([FromBody] SetChannelLogLevelInput input) =>
|
||||
App.GetService<IChannelPageService>().SetChannelLogLevelAsync(input.Id, input.LogLevel);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task SetDeviceLogLevelAsync([FromBody] SetDeviceLogLevelInput input) =>
|
||||
App.GetService<IDevicePageService>().SetDeviceLogLevelAsync(input.Id, input.LogLevel);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task SetRedundancyLogLevelAsync(LogLevel logLevel) =>
|
||||
App.GetService<IRedundancyHostedService>().SetRedundancyLogLevelAsync(logLevel);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task SetRulesLogLevelAsync([FromBody] SetRulesLogLevelInput input) =>
|
||||
App.GetService<IRulesEngineHostedService>().SetRulesLogLevelAsync(input.RulesId, input.LogLevel);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> StartBusinessChannelEnableAsync() => App.GetService<IChannelEnableService>().StartBusinessChannelEnableAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<bool> StartCollectChannelEnableAsync() => App.GetService<IChannelEnableService>().StartCollectChannelEnableAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task StartRedundancyTaskAsync() => App.GetService<IRedundancyHostedService>().StartRedundancyTaskAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task StopRedundancyTaskAsync() => App.GetService<IRedundancyHostedService>().StopRedundancyTaskAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<AuthorizeInfo> TryAuthorizeAsync(string password) => App.GetService<IAuthenticationService>().TryAuthorizeAsync(password);
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<AuthorizeInfo> TryGetAuthorizeInfoAsync() => App.GetService<IAuthenticationService>().TryGetAuthorizeInfoAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task UnAuthorizeAsync() => App.GetService<IAuthenticationService>().UnAuthorizeAsync();
|
||||
|
||||
[HttpPost]
|
||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||
public Task<string> UUIDAsync() => App.GetService<IAuthenticationService>().UUIDAsync();
|
||||
}
|
||||
// 定义请求 DTO
|
||||
public class BatchEditChannelRequest
|
||||
{
|
||||
public List<Channel> Models { get; set; }
|
||||
public Channel OldModel { get; set; }
|
||||
public Channel Model { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class BatchEditDeviceRequest
|
||||
{
|
||||
public List<Device> Models { get; set; }
|
||||
public Device OldModel { get; set; }
|
||||
public Device Model { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class BatchEditVariableRequest
|
||||
{
|
||||
public List<Variable> Models { get; set; }
|
||||
public Variable OldModel { get; set; }
|
||||
public Variable Model { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class BatchSaveVariableRequest
|
||||
{
|
||||
public List<Variable> Input { get; set; }
|
||||
public ItemChangedType Type { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
public class CopyChannelRequest
|
||||
{
|
||||
public int CopyCount { get; set; }
|
||||
public string CopyChannelNamePrefix { get; set; }
|
||||
public int CopyChannelNameSuffixNumber { get; set; }
|
||||
public string CopyDeviceNamePrefix { get; set; }
|
||||
public int CopyDeviceNameSuffixNumber { get; set; }
|
||||
public long ChannelId { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class CopyDeviceRequest
|
||||
{
|
||||
public int CopyCount { get; set; }
|
||||
public string CopyDeviceNamePrefix { get; set; }
|
||||
public int CopyDeviceNameSuffixNumber { get; set; }
|
||||
public long DeviceId { get; set; }
|
||||
public bool AutoRestartThread { get; set; }
|
||||
}
|
||||
|
||||
public class CopyVariableRequest
|
||||
{
|
||||
public List<Variable> Model { get; set; }
|
||||
public int CopyCount { get; set; }
|
||||
public string CopyVariableNamePrefix { get; set; }
|
||||
public int CopyVariableNameSuffixNumber { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
public class DeleteRequest
|
||||
{
|
||||
public List<long> Ids { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
public class GetListRequest<TOptions>
|
||||
{
|
||||
public TOptions Options { get; set; }
|
||||
public int Max { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class ImportChannelInput
|
||||
{
|
||||
public List<Channel> UpData { get; set; }
|
||||
public List<Channel> InsertData { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class ImportFileInput
|
||||
{
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class ImportUSheetInput
|
||||
{
|
||||
public USheetDatas Input { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class InsertTestDataInput
|
||||
{
|
||||
public int TestVariableCount { get; set; }
|
||||
public int TestDeviceCount { get; set; }
|
||||
public string SlaveUrl { get; set; }
|
||||
public bool BusinessEnable { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class LastLogDataInput
|
||||
{
|
||||
public string File { get; set; }
|
||||
public int LineCount { get; set; } = 200;
|
||||
}
|
||||
|
||||
public class OnDeviceSelectedItemQueryInput
|
||||
{
|
||||
public VirtualizeQueryOption Option { get; set; }
|
||||
public bool IsCollect { get; set; }
|
||||
}
|
||||
|
||||
public class OnRedundantDevicesQueryInput
|
||||
{
|
||||
public VirtualizeQueryOption Option { get; set; }
|
||||
public long DeviceId { get; set; }
|
||||
public long ChannelId { get; set; }
|
||||
}
|
||||
|
||||
public class SaveChannelInput
|
||||
{
|
||||
public Channel Input { get; set; }
|
||||
public ItemChangedType Type { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class SaveDeviceInput
|
||||
{
|
||||
public Device Input { get; set; }
|
||||
public ItemChangedType Type { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class SaveVariableInput
|
||||
{
|
||||
public Variable Input { get; set; }
|
||||
public ItemChangedType Type { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
public class SetChannelLogLevelInput
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public LogLevel LogLevel { get; set; }
|
||||
}
|
||||
|
||||
public class SetDeviceLogLevelInput
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public LogLevel LogLevel { get; set; }
|
||||
}
|
||||
|
||||
public class SetRulesLogLevelInput
|
||||
{
|
||||
public long RulesId { get; set; }
|
||||
public TouchSocket.Core.LogLevel LogLevel { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class ImportRequest
|
||||
{
|
||||
public IFormFile File { get; set; }
|
||||
public bool Restart { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class PluginQueryPageOptions
|
||||
{
|
||||
public QueryPageOptions Options { get; set; }
|
||||
public PluginTypeEnum PluginType { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class KVQueryPageOptions
|
||||
{
|
||||
public QueryPageOptions Options { get; set; }
|
||||
public FilterKeyValueAction FilterKeyValueAction { get; set; }
|
||||
}
|
@@ -27,7 +27,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
[ApiController]
|
||||
[RolePermission]
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
[TouchSocket.WebApi.Router("/miniapi/[api]/[action]")]
|
||||
[TouchSocket.WebApi.Router("/miniapi/runtimeinfo/[action]")]
|
||||
[TouchSocket.WebApi.EnableCors("cors")]
|
||||
public class RuntimeInfoController : ControllerBase, IRpcServer
|
||||
{
|
||||
|
@@ -18,7 +18,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
[Route("api/[controller]/[action]")]
|
||||
[AllowAnonymous]
|
||||
[ApiController]
|
||||
[TouchSocket.WebApi.Router("/miniapi/[api]/[action]")]
|
||||
[TouchSocket.WebApi.Router("/miniapi/test/[action]")]
|
||||
[TouchSocket.WebApi.EnableCors("cors")]
|
||||
public class TestController : ControllerBase, IRpcServer
|
||||
{
|
||||
|
@@ -26,8 +26,21 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
protected abstract BusinessPropertyWithCache _businessPropertyWithCache { get; }
|
||||
|
||||
#if !Management
|
||||
|
||||
protected override Task DisposeAsync(bool disposing)
|
||||
{
|
||||
// 清空内存队列
|
||||
_memoryPluginEventDataModelQueue.Clear();
|
||||
_memoryAlarmModelQueue.Clear();
|
||||
_memoryDevModelQueue.Clear();
|
||||
_memoryVarModelQueue.Clear();
|
||||
_memoryVarModelsQueue.Clear();
|
||||
return base.DisposeAsync(disposing);
|
||||
}
|
||||
|
||||
#region 条件
|
||||
|
||||
protected abstract bool PluginEventDataModelEnable { get; }
|
||||
protected abstract bool AlarmModelEnable { get; }
|
||||
protected abstract bool DevModelEnable { get; }
|
||||
protected abstract bool VarModelEnable { get; }
|
||||
@@ -36,6 +49,10 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
if (AlarmModelEnable)
|
||||
DBCacheAlarm = LocalDBCacheAlarmModel();
|
||||
|
||||
if (PluginEventDataModelEnable)
|
||||
DBCachePluginEventData = LocalDBCachePluginEventDataModel();
|
||||
|
||||
|
||||
if (DevModelEnable)
|
||||
DBCacheDev = LocalDBCacheDevModel();
|
||||
|
||||
@@ -68,7 +85,10 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
{
|
||||
await UpdateAlarmModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (PluginEventDataModelEnable)
|
||||
{
|
||||
await UpdatePluginEventDataModelMemory(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
if (VarModelEnable)
|
||||
{
|
||||
await UpdateVarModelCache(cancellationToken).ConfigureAwait(false);
|
||||
@@ -83,6 +103,12 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
if (AlarmModelEnable)
|
||||
{
|
||||
await UpdateAlarmModelCache(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
if (PluginEventDataModelEnable)
|
||||
{
|
||||
await UpdatePluginEventDataModelCache(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -90,10 +116,12 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
#region alarm
|
||||
|
||||
protected ConcurrentQueue<CacheDBItem<AlarmVariable>> _memoryAlarmModelQueue = new();
|
||||
protected ConcurrentQueue<CacheDBItem<PluginEventData>> _memoryPluginEventDataModelQueue = new();
|
||||
|
||||
private volatile bool LocalDBCacheAlarmModelInited;
|
||||
private CacheDB DBCacheAlarm;
|
||||
|
||||
private volatile bool LocalDBCachePluginEventDataModelInited;
|
||||
private CacheDB DBCachePluginEventData;
|
||||
/// <summary>
|
||||
/// 入缓存
|
||||
/// </summary>
|
||||
@@ -142,6 +170,55 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddCache(List<CacheDBItem<PluginEventData>> data)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogMessage?.LogInformation($"Add {typeof(PluginEventData).Name} data to file cache, count {data.Count}");
|
||||
foreach (var item in data)
|
||||
{
|
||||
item.Id = CommonUtils.GetSingleId();
|
||||
}
|
||||
var dir = CacheDBUtil.GetCacheFilePath(CurrentDevice.Name.ToString());
|
||||
var fileStart = CacheDBUtil.GetFileName($"{CurrentDevice.PluginName}_{typeof(PluginEventData).FullName}_{nameof(PluginEventData)}");
|
||||
var fullName = dir.CombinePathWithOs($"{fileStart}{CacheDBUtil.EX}");
|
||||
|
||||
lock (cacheLock)
|
||||
{
|
||||
bool s = false;
|
||||
while (!s)
|
||||
{
|
||||
s = CacheDBUtil.DeleteCache(_businessPropertyWithCache.CacheFileMaxLength, fullName);
|
||||
}
|
||||
using var cache = LocalDBCachePluginEventDataModel();
|
||||
cache.DBProvider.Fastest<CacheDBItem<PluginEventData>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
using var cache = LocalDBCachePluginEventDataModel();
|
||||
lock (cache.CacheDBOption)
|
||||
{
|
||||
cache.DBProvider.Fastest<CacheDBItem<PluginEventData>>().PageSize(50000).BulkCopy(data);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, "Add cache fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 添加队列,超限后会入缓存
|
||||
/// </summary>
|
||||
@@ -183,6 +260,48 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加队列,超限后会入缓存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
protected virtual void AddQueuePluginDataModel(CacheDBItem<PluginEventData> data)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
//检测队列长度,超限存入缓存数据库
|
||||
if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
List<CacheDBItem<PluginEventData>> list = null;
|
||||
lock (_memoryPluginEventDataModelQueue)
|
||||
{
|
||||
if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
list = _memoryPluginEventDataModelQueue.ToListWithDequeue();
|
||||
}
|
||||
}
|
||||
AddCache(list);
|
||||
}
|
||||
}
|
||||
if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
lock (_memoryPluginEventDataModelQueue)
|
||||
{
|
||||
if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
|
||||
{
|
||||
LogMessage?.LogWarning($"{typeof(PluginEventData).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase {_businessPropertyWithCache.QueueMaxCount} or Enable cache");
|
||||
_memoryPluginEventDataModelQueue.Clear();
|
||||
_memoryPluginEventDataModelQueue.Enqueue(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryPluginEventDataModelQueue.Enqueue(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存对象,注意每次获取的对象可能不一样,如顺序操作,需固定引用
|
||||
/// </summary>
|
||||
@@ -197,6 +316,20 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
}
|
||||
return cacheDb;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取缓存对象,注意每次获取的对象可能不一样,如顺序操作,需固定引用
|
||||
/// </summary>
|
||||
protected virtual CacheDB LocalDBCachePluginEventDataModel()
|
||||
{
|
||||
var cacheDb = CacheDBUtil.GetCache(typeof(CacheDBItem<PluginEventData>), CurrentDevice.Name.ToString(), $"{CurrentDevice.PluginName}_{typeof(PluginEventData).Name}");
|
||||
|
||||
if (!LocalDBCachePluginEventDataModelInited)
|
||||
{
|
||||
cacheDb.InitDb();
|
||||
LocalDBCachePluginEventDataModelInited = true;
|
||||
}
|
||||
return cacheDb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 需实现上传到通道
|
||||
@@ -206,6 +339,16 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
/// <returns></returns>
|
||||
protected abstract ValueTask<OperResult> UpdateAlarmModel(List<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 需实现上传到通道
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
protected abstract ValueTask<OperResult> UpdatePluginEventDataModel(List<CacheDBItem<PluginEventData>> item, CancellationToken cancellationToken);
|
||||
|
||||
|
||||
protected async Task UpdateAlarmModelCache(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
@@ -262,10 +405,69 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion //成功上传时,补上传缓存数据
|
||||
}
|
||||
}
|
||||
protected async Task UpdatePluginEventDataModelCache(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_businessPropertyWithCache.CacheEnable)
|
||||
{
|
||||
#region //成功上传时,补上传缓存数据
|
||||
|
||||
if (IsConnected())
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
//循环获取,固定读最大行数量,执行完成需删除行
|
||||
var varList = await DBCachePluginEventData.DBProvider.Queryable<CacheDBItem<PluginEventData>>().Take(_businessPropertyWithCache.SplitSize).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (varList.Count != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdatePluginEventDataModel(varList, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
//删除缓存
|
||||
await DBCachePluginEventData.DBProvider.Deleteable<CacheDBItem<PluginEventData>>(varList).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion //成功上传时,补上传缓存数据
|
||||
}
|
||||
}
|
||||
protected async Task UpdateAlarmModelMemory(CancellationToken cancellationToken)
|
||||
{
|
||||
#region //上传设备内存队列中的数据
|
||||
@@ -307,6 +509,47 @@ public abstract class BusinessBaseWithCache : BusinessBase
|
||||
|
||||
#endregion //上传设备内存队列中的数据
|
||||
}
|
||||
protected async Task UpdatePluginEventDataModelMemory(CancellationToken cancellationToken)
|
||||
{
|
||||
#region //上传设备内存队列中的数据
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var list = _memoryPluginEventDataModelQueue.ToListWithDequeue().ChunkBetter(_businessPropertyWithCache.SplitSize);
|
||||
foreach (var item in list)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await UpdatePluginEventDataModel(item, cancellationToken).ConfigureAwait(false);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
AddCache(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (success)
|
||||
LogMessage?.LogWarning(ex);
|
||||
success = false;
|
||||
}
|
||||
#endregion //上传设备内存队列中的数据
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
@@ -20,6 +20,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache
|
||||
{
|
||||
#if !Management
|
||||
protected override bool PluginEventDataModelEnable => true;
|
||||
protected override bool AlarmModelEnable => true;
|
||||
|
||||
protected override bool DevModelEnable => false;
|
||||
@@ -57,14 +58,40 @@ public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache
|
||||
GlobalData.AlarmChangedEvent -= AlarmValueChange;
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a => AlarmValueChange(a.Value));
|
||||
GlobalData.AlarmChangedEvent += AlarmValueChange;
|
||||
GlobalData.PluginEventHandler -= PluginEventChange;
|
||||
GlobalData.PluginEventHandler += PluginEventChange;
|
||||
|
||||
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
protected override Task DisposeAsync(bool disposing)
|
||||
{
|
||||
GlobalData.AlarmChangedEvent -= AlarmValueChange;
|
||||
GlobalData.PluginEventHandler -= PluginEventChange;
|
||||
return base.DisposeAsync(disposing);
|
||||
}
|
||||
|
||||
|
||||
private void PluginEventChange(PluginEventData value)
|
||||
{
|
||||
if (CurrentDevice?.Pause != false)
|
||||
return;
|
||||
if (TaskSchedulerLoop?.Stoped == true) return;
|
||||
|
||||
if (!PluginEventDataModelEnable) return;
|
||||
// 如果业务属性的缓存为间隔上传,则不执行后续操作
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
PluginChange(value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueuePluginDataModel(CacheDBItem{PluginEventData})"/> 方法。
|
||||
/// </summary>
|
||||
protected virtual void PluginChange(PluginEventData value)
|
||||
{
|
||||
// 在报警状态变化时执行的自定义逻辑
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。
|
||||
/// </summary>
|
||||
|
@@ -40,7 +40,13 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a => AlarmValueChange(a.Value));
|
||||
|
||||
GlobalData.AlarmChangedEvent += AlarmValueChange;
|
||||
// 解绑全局数据的事件
|
||||
|
||||
}
|
||||
if (PluginEventDataModelEnable)
|
||||
{
|
||||
GlobalData.PluginEventHandler -= PluginEventChange;
|
||||
GlobalData.PluginEventHandler += PluginEventChange;
|
||||
|
||||
}
|
||||
if (DevModelEnable)
|
||||
{
|
||||
@@ -64,7 +70,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
}
|
||||
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (AlarmModelEnable || DevModelEnable || VarModelEnable)
|
||||
if (AlarmModelEnable || DevModelEnable || VarModelEnable || PluginEventDataModelEnable)
|
||||
{
|
||||
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
|
||||
if (_businessPropertyWithCacheInterval.IsAllVariable)
|
||||
@@ -137,14 +143,12 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
{
|
||||
// 解绑事件
|
||||
GlobalData.AlarmChangedEvent -= AlarmValueChange;
|
||||
GlobalData.PluginEventHandler -= PluginEventChange;
|
||||
|
||||
GlobalData.VariableValueChangeEvent -= VariableValueChange;
|
||||
GlobalData.DeviceStatusChangeEvent -= DeviceStatusChange;
|
||||
|
||||
// 清空内存队列
|
||||
_memoryAlarmModelQueue.Clear();
|
||||
_memoryDevModelQueue.Clear();
|
||||
_memoryVarModelQueue.Clear();
|
||||
_memoryVarModelsQueue.Clear();
|
||||
|
||||
return base.DisposeAsync(disposing);
|
||||
}
|
||||
|
||||
@@ -226,7 +230,26 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
{
|
||||
// 在变量状态变化时执行的自定义逻辑
|
||||
}
|
||||
private void PluginEventChange(PluginEventData value)
|
||||
{
|
||||
if (CurrentDevice?.Pause != false)
|
||||
return;
|
||||
if (TaskSchedulerLoop?.Stoped == true) return;
|
||||
|
||||
if (!PluginEventDataModelEnable) return;
|
||||
// 如果业务属性的缓存为间隔上传,则不执行后续操作
|
||||
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
|
||||
{
|
||||
PluginChange(value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueAlarmModel"/> 方法。
|
||||
/// </summary>
|
||||
protected virtual void PluginChange(PluginEventData value)
|
||||
{
|
||||
// 在报警状态变化时执行的自定义逻辑
|
||||
}
|
||||
/// <summary>
|
||||
/// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。
|
||||
/// </summary>
|
||||
|
@@ -26,8 +26,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
|
||||
protected abstract BusinessPropertyWithCacheIntervalScript _businessPropertyWithCacheIntervalScript { get; }
|
||||
|
||||
#if !Management
|
||||
|
||||
|
||||
#if !Management
|
||||
protected internal override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
|
||||
{
|
||||
CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptPluginEventDataModel);
|
||||
CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
return base.InitChannelAsync(channel, cancellationToken);
|
||||
}
|
||||
public virtual string[] Match(string input)
|
||||
{
|
||||
// 生成缓存键,以确保缓存的唯一性
|
||||
@@ -64,7 +73,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
|
||||
protected IEnumerable<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmVariable> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmVariable>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmVariable>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel, LogMessage);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
|
||||
if (topics.Length > 0)
|
||||
{
|
||||
@@ -88,16 +97,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
|
||||
{
|
||||
var gList = group.ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
@@ -110,23 +122,92 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
|
||||
{
|
||||
var gList = data.ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count);
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected IEnumerable<TopicArray> GetPluginEventDataTopicArrays(IEnumerable<PluginEventData> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<PluginEventData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptPluginEventDataModel, LogMessage);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic);
|
||||
if (topics.Length > 0)
|
||||
{
|
||||
{
|
||||
//获取分组最终结果
|
||||
var groups = data.GroupByKeys(topics);
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
// 上传主题
|
||||
// 获取预定义的报警主题
|
||||
string topic = _businessPropertyWithCacheIntervalScript.PluginEventDataTopic;
|
||||
|
||||
// 将主题中的占位符替换为分组键对应的值
|
||||
for (int i = 0; i < topics.Length; i++)
|
||||
{
|
||||
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
|
||||
}
|
||||
|
||||
// 上传内容
|
||||
if (_businessPropertyWithCacheIntervalScript.IsPluginEventDataList)
|
||||
{
|
||||
var gList = group.ToList();
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_businessPropertyWithCacheIntervalScript.IsPluginEventDataList)
|
||||
{
|
||||
var gList = data.ToList();
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic, json, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected IEnumerable<TopicArray> GetDeviceTopicArray(IEnumerable<DeviceBasicData> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<DeviceBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<DeviceBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel, LogMessage);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
|
||||
if (topics.Length > 0)
|
||||
{
|
||||
@@ -152,16 +233,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
{
|
||||
// 如果是设备列表,则将整个分组转换为 JSON 字符串
|
||||
var gList = group.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不是设备列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
@@ -175,14 +259,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
|
||||
{
|
||||
var gList = data.ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count);
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1);
|
||||
}
|
||||
}
|
||||
@@ -191,7 +278,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
|
||||
protected IEnumerable<TopicArray> GetVariableScriptTopicArray(IEnumerable<VariableBasicData> item)
|
||||
{
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<VariableBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
|
||||
var data = Application.DynamicModelExtension.GetDynamicModel<VariableBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel, LogMessage);
|
||||
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
|
||||
if (topics.Length > 0)
|
||||
{
|
||||
@@ -217,16 +304,20 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
{
|
||||
// 如果是变量列表,则将整个分组转换为 JSON 字符串
|
||||
var gList = group.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
@@ -240,14 +331,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
|
||||
{
|
||||
var gList = data.ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1);
|
||||
}
|
||||
}
|
||||
@@ -294,16 +388,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
{
|
||||
// 如果是变量列表,则将整个分组转换为 JSON 字符串
|
||||
var gList = group.Select(a => a).ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
|
||||
foreach (var gro in group)
|
||||
{
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
// 将主题和 JSON 内容添加到列表中
|
||||
yield return new(topic, json, 1);
|
||||
}
|
||||
@@ -317,14 +414,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
|
||||
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
|
||||
{
|
||||
var gList = data.ToList();
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
|
||||
if (gList.Count > 0)
|
||||
{
|
||||
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in data)
|
||||
{
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
|
||||
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
|
||||
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1);
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
public abstract partial class BusinessBaseWithCacheIntervalScriptAll : BusinessBaseWithCacheIntervalScript
|
||||
{
|
||||
#if !Management
|
||||
protected override bool PluginEventDataModelEnable => true;
|
||||
protected override bool AlarmModelEnable => true;
|
||||
|
||||
protected override bool DevModelEnable => true;
|
||||
|
@@ -16,6 +16,7 @@ namespace ThingsGateway.Gateway.Application;
|
||||
public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCacheInterval
|
||||
{
|
||||
#if !Management
|
||||
protected override bool PluginEventDataModelEnable => false;
|
||||
protected override bool AlarmModelEnable => false;
|
||||
|
||||
protected override bool DevModelEnable => false;
|
||||
@@ -30,5 +31,9 @@ public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCa
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
protected override ValueTask<OperResult> UpdatePluginEventDataModel(List<CacheDBItem<PluginEventData>> item, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@@ -28,7 +28,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public bool JsonFormattingIndented { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 忽略Null值
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public bool JsonIgnoreNull { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 设备Topic
|
||||
/// </summary>
|
||||
@@ -46,6 +50,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public bool IsAlarmList { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 报警Topic
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
public bool IsPluginEventDataList { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 设备Topic
|
||||
@@ -65,6 +74,12 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
|
||||
[DynamicProperty(Remark = "可使用${key}作为匹配项,key必须是上传实体中的属性,比如ThingsGateway/Alarm/${DeviceName}")]
|
||||
public string AlarmTopic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报警Topic
|
||||
/// </summary>
|
||||
[DynamicProperty(Remark = "可使用${key}作为匹配项,key必须是上传实体中的属性,比如ThingsGateway/PluginEventData/${DeviceName}")]
|
||||
public string PluginEventDataTopic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备实体脚本
|
||||
/// </summary>
|
||||
@@ -85,4 +100,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
|
||||
public string? BigTextScriptAlarmModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报警实体脚本
|
||||
/// </summary>
|
||||
[DynamicProperty]
|
||||
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
|
||||
public string? BigTextScriptPluginEventDataModel { get; set; }
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class BusinessVariableProperty : VariablePropertyBase
|
||||
{
|
||||
[DynamicProperty]
|
||||
public string Data1 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data2 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data3 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data4 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data5 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data6 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data7 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data8 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data9 { get; set; }
|
||||
[DynamicProperty]
|
||||
public string Data10 { get; set; }
|
||||
}
|
@@ -366,8 +366,17 @@ public abstract partial class CollectBase : DriverBase
|
||||
|
||||
if (Pause) return;
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
CancellationToken readToken = default;
|
||||
var readerLockTask = ReadWriteLock.ReaderLockAsync(cancellationToken);
|
||||
if (!readerLockTask.IsCompleted)
|
||||
{
|
||||
readToken = await readerLockTask.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
readToken = readerLockTask.Result;
|
||||
}
|
||||
|
||||
var readToken = await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (readToken.IsCancellationRequested)
|
||||
{
|
||||
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||
@@ -379,7 +388,17 @@ public abstract partial class CollectBase : DriverBase
|
||||
|
||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||
var readResult = await ReadSourceAsync(variableSourceRead, allToken).ConfigureAwait(false);
|
||||
|
||||
OperResult<ReadOnlyMemory<byte>> readResult = default;
|
||||
var readTask = ReadSourceAsync(variableSourceRead, allToken);
|
||||
if (!readTask.IsCompleted)
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
|
||||
var readErrorCount = 0;
|
||||
|
||||
@@ -403,7 +422,16 @@ public abstract partial class CollectBase : DriverBase
|
||||
|
||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||
readResult = await ReadSourceAsync(variableSourceRead, allToken).ConfigureAwait(false);
|
||||
var readTask1 = ReadSourceAsync(variableSourceRead, allToken);
|
||||
if (!readTask1.IsCompleted)
|
||||
{
|
||||
readResult = await readTask1.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = readTask1.Result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (readResult.IsSuccess)
|
||||
|
@@ -159,7 +159,16 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
return new(new OperationCanceledException());
|
||||
|
||||
// 从协议读取数据
|
||||
var read = await FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken).ConfigureAwait(false);
|
||||
OperResult<ReadOnlyMemory<byte>> read = default;
|
||||
var readTask = FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
||||
if (!readTask.IsCompleted)
|
||||
{
|
||||
read = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
read = readTask.Result;
|
||||
}
|
||||
|
||||
// 如果读取成功且有有效内容,则解析结构化内容
|
||||
if (read.IsSuccess)
|
||||
|
@@ -23,12 +23,16 @@ public static class DynamicModelExtension
|
||||
/// <summary>
|
||||
/// GetDynamicModel
|
||||
/// </summary>
|
||||
public static IEnumerable<object> GetDynamicModel<T>(this IEnumerable<T> datas, string script)
|
||||
public static IEnumerable<object> GetDynamicModel<T>(this IEnumerable<T> datas, string script, TouchSocket.Core.ILog log)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(script))
|
||||
{
|
||||
//执行脚本,获取新实体
|
||||
var getDeviceModel = CSharpScriptEngineExtension.Do<IDynamicModel>(script);
|
||||
if (getDeviceModel is DynamicModelBase dynamicModelBase)
|
||||
{
|
||||
dynamicModelBase.Logger = log;
|
||||
}
|
||||
return getDeviceModel.GetList(datas?.Cast<object>());
|
||||
}
|
||||
else
|
||||
@@ -50,9 +54,17 @@ public static class DynamicModelExtension
|
||||
if (variableRuntime == null || propertyName.IsNullOrWhiteSpace())
|
||||
return null;
|
||||
|
||||
// 检查是否存在对应的业务设备Id
|
||||
if (variableRuntime.VariablePropertys?.TryGetValue(businessId, out var keyValuePairs) == true)
|
||||
{
|
||||
keyValuePairs.TryGetValue(propertyName, out var value);
|
||||
return value; // 返回属性值
|
||||
}
|
||||
|
||||
|
||||
if (GlobalData.IdDevices.TryGetValue(businessId, out var deviceRuntime))
|
||||
{
|
||||
if (deviceRuntime.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property)
|
||||
if (deviceRuntime.Driver is BusinessBase businessBase && businessBase.DriverProperties is IBusinessPropertyAllVariableBase property)
|
||||
{
|
||||
if (property.IsAllVariable)
|
||||
{
|
||||
@@ -64,18 +76,12 @@ public static class DynamicModelExtension
|
||||
}
|
||||
else
|
||||
{
|
||||
return ThingsGatewayStringConverter.Default.Serialize(null, property.GetValue(propertyName, false));
|
||||
return ThingsGatewayStringConverter.Default.Serialize(null, businessBase.VariablePropertys.GetValue(propertyName, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否存在对应的业务设备Id
|
||||
if (variableRuntime.VariablePropertys?.ContainsKey(businessId) == true)
|
||||
{
|
||||
variableRuntime.VariablePropertys[businessId].TryGetValue(propertyName, out var value);
|
||||
return value; // 返回属性值
|
||||
}
|
||||
|
||||
return null; // 未找到对应的业务设备Id,返回null
|
||||
}
|
||||
@@ -127,3 +133,8 @@ public interface IDynamicModel
|
||||
{
|
||||
IEnumerable<dynamic> GetList(IEnumerable<object> datas);
|
||||
}
|
||||
public abstract class DynamicModelBase : IDynamicModel
|
||||
{
|
||||
public TouchSocket.Core.ILog Logger { get; set; }
|
||||
public abstract IEnumerable<dynamic> GetList(IEnumerable<object> datas);
|
||||
}
|
||||
|
@@ -10,8 +10,6 @@
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
@@ -35,7 +33,7 @@ public class BackendLog : PrimaryIdEntity
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "日志级别", IsNullable = false)]
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
public LogLevel LogLevel { get; set; }
|
||||
public Microsoft.Extensions.Logging.LogLevel LogLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志来源
|
||||
|
@@ -40,6 +40,28 @@ public delegate void VariableCollectEventHandler(VariableRuntime variableRuntime
|
||||
/// </summary>
|
||||
public delegate void VariableAlarmEventHandler(AlarmVariable alarmVariable);
|
||||
|
||||
public delegate void PluginEventHandler(PluginEventData data);
|
||||
|
||||
public class PluginEventData
|
||||
{
|
||||
public string DeviceName { get; set; }
|
||||
public Newtonsoft.Json.Linq.JObject Value { get; set; }
|
||||
public string ValueType { get; set; }
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
public object ObjectValue { get; set; }
|
||||
//public object ObjectValue => ValueType.IsNullOrEmpty() ? Value : Value.ToObject(Type.GetType(ValueType, false) ?? typeof(JObject));
|
||||
|
||||
public PluginEventData(string deviceName, object value)
|
||||
{
|
||||
DeviceName = deviceName;
|
||||
ObjectValue = value;
|
||||
Value = Newtonsoft.Json.Linq.JObject.FromObject(value);
|
||||
ValueType = $"{value?.GetType().FullName},{value?.GetType().Assembly.GetName().Name}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 采集设备值与状态全局提供类,用于提供全局的设备状态和变量数据的管理
|
||||
/// </summary>
|
||||
@@ -64,6 +86,8 @@ public static class GlobalData
|
||||
/// </summary>
|
||||
public static event VariableAlarmEventHandler? AlarmChangedEvent;
|
||||
|
||||
public static event PluginEventHandler? PluginEventHandler;
|
||||
|
||||
public static async Task<IEnumerable<ChannelRuntime>> GetCurrentUserChannels()
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
@@ -480,6 +504,14 @@ public static class GlobalData
|
||||
AlarmChangedEvent?.Invoke(alarmVariable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事件状态变化处理方法,用于处理事件状态变化时的逻辑
|
||||
/// </summary>
|
||||
public static void PluginEventChange(PluginEventData pluginEventData)
|
||||
{
|
||||
// 触发设备状态变化事件,并将设备运行时对象转换为设备数据对象进行传递
|
||||
PluginEventHandler?.Invoke(pluginEventData);
|
||||
}
|
||||
/// <summary>
|
||||
/// 设备状态变化处理方法,用于处理设备状态变化时的逻辑
|
||||
/// </summary>
|
||||
|
@@ -61,7 +61,7 @@ public class LogJob : IJob
|
||||
|
||||
//网关通道日志以通道id命名
|
||||
var rulesService = App.RootServices.GetService<IRulesService>();
|
||||
var ruleNames = (await rulesService.GetAllAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet();
|
||||
var ruleNames = (await rulesService.GetAllRulesAsync().ConfigureAwait(false)).Select(a => a.Name.ToString()).ToHashSet();
|
||||
var ruleBaseDir = RulesEngineHostedService.LogDir;
|
||||
Directory.CreateDirectory(ruleBaseDir);
|
||||
|
||||
|
@@ -1,4 +1,21 @@
|
||||
{
|
||||
|
||||
"ThingsGateway.Gateway.Application.BusinessVariableProperty": {
|
||||
|
||||
"Data1": "Data1",
|
||||
"Data2": "Data2",
|
||||
"Data3": "Data3",
|
||||
"Data4": "Data4",
|
||||
"Data5": "Data5",
|
||||
"Data6": "Data6",
|
||||
"Data7": "Data7",
|
||||
"Data8": "Data8",
|
||||
"Data9": "Data9",
|
||||
"Data10": "Data10"
|
||||
},
|
||||
|
||||
|
||||
|
||||
"ThingsGateway.Management.Application.ManagementExportString": {
|
||||
|
||||
"ManagementConfigName": "ManagementConfigName"
|
||||
@@ -30,6 +47,7 @@
|
||||
|
||||
"Actuator": "Actuator",
|
||||
"AlarmChangedTriggerNode": "AlarmStateTrigger",
|
||||
"PluginEventChangedTriggerNode": "PluginEventTrigger",
|
||||
"BusinessNode": "BusinessDeviceExecution",
|
||||
"BusinessNode.Placeholder": "BusinessDeviceName",
|
||||
"Cancel": "Cancel",
|
||||
@@ -225,15 +243,19 @@
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.BusinessPropertyWithCacheIntervalScript": {
|
||||
"AlarmTopic": "AlarmTopic",
|
||||
"PluginEventDataTopic": "PluginEventDataTopic",
|
||||
"BigTextScriptAlarmModel": "BigTextScriptAlarmModel",
|
||||
"BigTextScriptPluginEventDataModel": "BigTextScriptPluginEventDataModel",
|
||||
"BigTextScriptDeviceModel": "BigTextScriptDeviceModel",
|
||||
"BigTextScriptVariableModel": "BigTextScriptVariableModel",
|
||||
"DetailLog": "DetailLog",
|
||||
"DeviceTopic": "DeviceTopic",
|
||||
"IsAlarmList": "IsAlarmList",
|
||||
"IsPluginEventDataList": "IsPluginEventDataList",
|
||||
"IsDeviceList": "IsDeviceList",
|
||||
"IsVariableList": "IsVariableList",
|
||||
"JsonFormattingIndented": "JsonFormattingIndented",
|
||||
"JsonIgnoreNull": "JsonIgnoreNull",
|
||||
"VariableTopic": "VariableTopic"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.BusinessUpdateEnum": {
|
||||
|
@@ -1,4 +1,18 @@
|
||||
{
|
||||
"ThingsGateway.Gateway.Application.BusinessVariableProperty": {
|
||||
|
||||
"Data1": "备注1",
|
||||
"Data2": "备注2",
|
||||
"Data3": "备注3",
|
||||
"Data4": "备注4",
|
||||
"Data5": "备注5",
|
||||
"Data6": "备注6",
|
||||
"Data7": "备注7",
|
||||
"Data8": "备注8",
|
||||
"Data9": "备注9",
|
||||
"Data10": "备注10"
|
||||
},
|
||||
|
||||
"ThingsGateway.Management.Application.ManagementExportString": {
|
||||
|
||||
"ManagementConfigName": "通讯配置"
|
||||
@@ -32,6 +46,7 @@
|
||||
|
||||
"Actuator": "执行",
|
||||
"AlarmChangedTriggerNode": "报警状态触发器",
|
||||
"PluginEventChangedTriggerNode": "插件事件触发器",
|
||||
"BusinessNode": "业务设备执行",
|
||||
"BusinessNode.Placeholder": "设备名称",
|
||||
"Cancel": "取消",
|
||||
@@ -227,15 +242,19 @@
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.BusinessPropertyWithCacheIntervalScript": {
|
||||
"AlarmTopic": "报警主题",
|
||||
"PluginEventDataTopic": "插件事件主题",
|
||||
"BigTextScriptAlarmModel": "报警上传脚本",
|
||||
"BigTextScriptPluginEventDataModel": "插件事件上传脚本",
|
||||
"BigTextScriptDeviceModel": "设备上传脚本",
|
||||
"BigTextScriptVariableModel": "变量上传脚本",
|
||||
"DetailLog": "详细日志",
|
||||
"DeviceTopic": "设备主题",
|
||||
"IsAlarmList": "报警列表上传",
|
||||
"IsPluginEventDataList": "插件事件列表上传",
|
||||
"IsDeviceList": "设备状态列表上传",
|
||||
"IsVariableList": "变量列表上传",
|
||||
"JsonFormattingIndented": "Json缩进格式化",
|
||||
"JsonIgnoreNull": "Json忽略null值",
|
||||
"VariableTopic": "变量主题"
|
||||
},
|
||||
"ThingsGateway.Gateway.Application.BusinessUpdateEnum": {
|
||||
|
@@ -67,4 +67,9 @@ public static partial class GatewayMapper
|
||||
public static partial Device AdaptDevice(this Device src);
|
||||
public static partial Variable AdaptVariable(this Variable src);
|
||||
public static partial List<PluginInfo> AdaptListPluginInfo(this List<PluginInfo> src);
|
||||
|
||||
|
||||
public static partial Dictionary<string, ImportPreviewOutputBase> AdaptImportPreviewOutputBases(this Dictionary<string, ImportPreviewOutputBase> src);
|
||||
|
||||
|
||||
}
|
||||
|
@@ -234,7 +234,9 @@ public partial class VariableRuntime : Variable
|
||||
private DateTime collectTime = DateTime.UnixEpoch.ToLocalTime();
|
||||
|
||||
#pragma warning disable CS0414
|
||||
#pragma warning disable CS0169
|
||||
private bool _isOnlineChanged;
|
||||
#pragma warning restore CS0169
|
||||
#pragma warning restore CS0414
|
||||
private bool _valueInited;
|
||||
|
||||
|
@@ -11,6 +11,7 @@
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.Extension.Generic;
|
||||
@@ -87,6 +88,12 @@ public class ChannelRuntimeService : IChannelRuntimeService
|
||||
|
||||
}
|
||||
|
||||
public async Task RestartChannelsAsync()
|
||||
{
|
||||
var data = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
|
||||
await RestartChannelAsync(data.ToList()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel)
|
||||
{
|
||||
if (GlobalData.IdChannels.TryGetValue(id, out var ChannelRuntime))
|
||||
@@ -395,6 +402,33 @@ public class ChannelRuntimeService : IChannelRuntimeService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IFormFile file, bool restart)
|
||||
{
|
||||
try
|
||||
{
|
||||
await WaitLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
var data = await GlobalData.ChannelService.PreviewAsync(file).ConfigureAwait(false);
|
||||
|
||||
if (data.Any(a => a.Value.HasError)) return data;
|
||||
var result = await GlobalData.ChannelService.ImportChannelAsync(data).ConfigureAwait(false);
|
||||
|
||||
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false);
|
||||
|
||||
RuntimeServiceHelper.Init(newChannelRuntimes);
|
||||
|
||||
//根据条件重启通道线程
|
||||
if (restart)
|
||||
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
WaitLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart)
|
||||
{
|
||||
|
@@ -11,6 +11,7 @@
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
using MiniExcelLibs;
|
||||
|
||||
@@ -409,8 +410,6 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile)
|
||||
{
|
||||
@@ -418,6 +417,13 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
return await PreviewAsync(path).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IFormFile browserFile)
|
||||
{
|
||||
var path = await browserFile.StorageLocal().ConfigureAwait(false);
|
||||
return await PreviewAsync(path).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string path)
|
||||
{
|
||||
@@ -468,7 +474,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
if (channel == null)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, unportNull));
|
||||
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, unportNull));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -491,7 +497,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
if (stringBuilder.Length > 0)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, stringBuilder.ToString()));
|
||||
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, stringBuilder.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -512,19 +518,19 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
|
||||
|
||||
if (channel.IsUp && ((dataScope != null && dataScope?.Count > 0 && !dataScope.Contains(channel.CreateOrgId)) || dataScope?.Count == 0 && channel.CreateUserId != UserManager.UserId))
|
||||
{
|
||||
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, "Operation not permitted"));
|
||||
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, "Operation not permitted"));
|
||||
}
|
||||
else
|
||||
{
|
||||
channels.Add(channel);
|
||||
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), true, null));
|
||||
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), true, null));
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, ex.Message));
|
||||
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, ex.Message));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
@@ -20,6 +20,7 @@ namespace ThingsGateway.Gateway.Application
|
||||
Task<string> GetPluginNameAsync(long channelId);
|
||||
|
||||
Task RestartChannelAsync(long channelId);
|
||||
Task RestartChannelsAsync();
|
||||
|
||||
Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id);
|
||||
Task SetChannelLogLevelAsync(long id, TouchSocket.Core.LogLevel logLevel);
|
||||
|
@@ -11,6 +11,7 @@
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
@@ -38,7 +39,5 @@ public interface IChannelRuntimeService : IChannelPageService
|
||||
Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart);
|
||||
Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart);
|
||||
Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart);
|
||||
|
||||
|
||||
|
||||
Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IFormFile file, bool restart);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user