diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/EntityTypeAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/EntityTypeAttribute.cs new file mode 100644 index 0000000..3610c00 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/EntityTypeAttribute.cs @@ -0,0 +1,19 @@ +using JiShe.CollectBus.IoTDB.Enums; + +namespace JiShe.CollectBus.IoTDB.Attribute +{ + /// + /// IoTDB实体类型特性 + /// + [AttributeUsage(AttributeTargets.Class)] + public class EntityTypeAttribute : System.Attribute + { + public EntityTypeEnum EntityType { get; } + + + public EntityTypeAttribute(EntityTypeEnum entityType) + { + EntityType = entityType; + } + } +} diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs index cebb85a..5f0ca07 100644 --- a/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/SingleMeasuringAttribute.cs @@ -1,7 +1,7 @@ namespace JiShe.CollectBus.IoTDB.Attribute { /// - /// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple,Item1=>测点名称,Item2=>测点值,泛型 + /// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple,Item1=>测点名称,Item2=>测点值,泛型 /// [AttributeUsage(AttributeTargets.Property)] public class SingleMeasuringAttribute : System.Attribute diff --git a/modules/JiShe.CollectBus.IoTDB/Attribute/TableNameOrTreePathAttribute.cs b/modules/JiShe.CollectBus.IoTDB/Attribute/TableNameOrTreePathAttribute.cs new file mode 100644 index 0000000..1b4f4f0 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Attribute/TableNameOrTreePathAttribute.cs @@ -0,0 +1,18 @@ +using JiShe.CollectBus.IoTDB.Enums; + +namespace JiShe.CollectBus.IoTDB.Attribute +{ + /// + /// IoTDB实体存储路径或表名称,一般用于已经明确的存储路径或表名称,例如日志存储 + /// + [AttributeUsage(AttributeTargets.Class)] + public class TableNameOrTreePathAttribute : System.Attribute + { + public string TableNameOrTreePath { get; } + + public TableNameOrTreePathAttribute(string tableNameOrTreePath) + { + TableNameOrTreePath = tableNameOrTreePath; + } + } +} diff --git a/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs b/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs index 2cce113..6d26bdc 100644 --- a/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs +++ b/modules/JiShe.CollectBus.IoTDB/CollectBusIoTDBModule.cs @@ -16,7 +16,7 @@ public class CollectBusIoTDbModule : AbpModule var configuration = context.Services.GetConfiguration(); Configure(options => { configuration.GetSection(nameof(IoTDbOptions)).Bind(options); }); - // 注册上下文为Scoped - context.Services.AddScoped(); + //// 注册上下文为Scoped + //context.Services.AddScoped(); } } \ No newline at end of file diff --git a/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs b/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs index f1619ac..ef68325 100644 --- a/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs +++ b/modules/JiShe.CollectBus.IoTDB/Context/IoTDBRuntimeContext.cs @@ -1,23 +1,24 @@ using JiShe.CollectBus.IoTDB.Options; using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.IoTDB.Context { /// /// IoTDB SessionPool 运行时上下文 /// - public class IoTDbRuntimeContext + public class IoTDBRuntimeContext: IScopedDependency { private readonly bool _defaultValue; - public IoTDbRuntimeContext(IOptions options) + public IoTDBRuntimeContext(IOptions options) { _defaultValue = options.Value.UseTableSessionPoolByDefault; UseTableSessionPool = _defaultValue; } /// - /// 是否使用表模型存储, 默认false,使用tree模型存储 + /// 存储模型切换标识,是否使用table模型存储, 默认为false,标识tree模型存储 /// public bool UseTableSessionPool { get; set; } diff --git a/modules/JiShe.CollectBus.IoTDB/EnumInfo/EntityTypeEnum.cs b/modules/JiShe.CollectBus.IoTDB/EnumInfo/EntityTypeEnum.cs new file mode 100644 index 0000000..26c6645 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/EnumInfo/EntityTypeEnum.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.IoTDB.Enums +{ + /// + /// IoTDB实体类型枚举 + /// + public enum EntityTypeEnum + { + /// + /// 树模型 + /// + TreeModel = 1, + + /// + /// 表模型 + /// + TableModel = 2, + } +} diff --git a/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs index 02ac3ee..82a0d47 100644 --- a/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs +++ b/modules/JiShe.CollectBus.IoTDB/Interface/IIoTDBProvider.cs @@ -1,4 +1,5 @@ using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IoTDB.Model; using JiShe.CollectBus.IoTDB.Options; using JiShe.CollectBus.IoTDB.Provider; @@ -31,6 +32,15 @@ namespace JiShe.CollectBus.IoTDB.Interface /// Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity; + /// + /// 批量插入数据 + /// + /// + /// 设备元数据 + /// + /// + Task BatchInsertAsync(DeviceMetadata deviceMetadata,IEnumerable entities) where T : IoTEntity; + /// /// 删除数据 @@ -38,7 +48,14 @@ namespace JiShe.CollectBus.IoTDB.Interface /// /// /// - Task DeleteAsync(QueryOptions options) where T : IoTEntity; + Task DeleteAsync(IoTDBQueryOptions options) where T : IoTEntity; + + /// + /// 获取设备元数据 + /// + /// + /// + Task GetMetadata() where T : IoTEntity; /// /// 查询数据 @@ -46,6 +63,6 @@ namespace JiShe.CollectBus.IoTDB.Interface /// /// /// - Task> QueryAsync(QueryOptions options) where T : IoTEntity, new(); + Task> QueryAsync(IoTDBQueryOptions options) where T : IoTEntity, new(); } } diff --git a/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj b/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj index dc8f2fb..8cc4b33 100644 --- a/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj +++ b/modules/JiShe.CollectBus.IoTDB/JiShe.CollectBus.IoTDB.csproj @@ -6,7 +6,6 @@ enable - diff --git a/modules/JiShe.CollectBus.IoTDB/Model/IoTEntity.cs b/modules/JiShe.CollectBus.IoTDB/Model/IoTEntity.cs new file mode 100644 index 0000000..92f98e1 --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Model/IoTEntity.cs @@ -0,0 +1,39 @@ +using JiShe.CollectBus.IoTDB.Attribute; + +namespace JiShe.CollectBus.IoTDB.Model +{ + /// + /// IoT实体基类,此类适用于多个数据测点记录场景,单个测点请使用子类 SingleMeasuring + /// + public abstract class IoTEntity + { + /// + /// 系统名称 + /// + [TAGColumn] + public string SystemName { get; set; } + + /// + /// 项目编码 + /// + [ATTRIBUTEColumn] + public string ProjectId { get; set; } + + /// + /// 设备类型集中器、电表、水表、流量计、传感器等 + /// + [ATTRIBUTEColumn] + public string DeviceType { get; set; } + + /// + /// 设备ID,也就是通信设备的唯一标识符,例如集中器地址,或者其他传感器设备地址 + /// + [TAGColumn] + public string DeviceId { get; set; } + + /// + /// 时标,也就是业务时间戳,单位毫秒,必须通过DateTimeOffset获取 + /// + public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + } +} diff --git a/modules/JiShe.CollectBus.IoTDB/Model/TableModelSingleMeasuringEntity.cs b/modules/JiShe.CollectBus.IoTDB/Model/TableModelSingleMeasuringEntity.cs new file mode 100644 index 0000000..4308dae --- /dev/null +++ b/modules/JiShe.CollectBus.IoTDB/Model/TableModelSingleMeasuringEntity.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Enums; +using JiShe.CollectBus.IoTDB.Provider; + +namespace JiShe.CollectBus.IoTDB.Model +{ + /// + /// Table模型单项数据实体 + /// + [EntityType(EntityTypeEnum.TableModel)] + public class TableModelSingleMeasuringEntity : IoTEntity + { + /// + /// 单项数据键值对 + /// + [SingleMeasuring(nameof(SingleColumn))] + public required Tuple SingleColumn { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs b/modules/JiShe.CollectBus.IoTDB/Model/TreeModelSingleMeasuringEntity.cs similarity index 51% rename from services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs rename to modules/JiShe.CollectBus.IoTDB/Model/TreeModelSingleMeasuringEntity.cs index 0440c1d..9b3609c 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/AFNEntity/SingleMeasuringAFNDataEntity.cs +++ b/modules/JiShe.CollectBus.IoTDB/Model/TreeModelSingleMeasuringEntity.cs @@ -4,19 +4,21 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Enums; using JiShe.CollectBus.IoTDB.Provider; -namespace JiShe.CollectBus.IotSystems.AFNEntity +namespace JiShe.CollectBus.IoTDB.Model { /// - /// AFN单项数据实体 + /// Tree模型单项数据实体 /// - public class SingleMeasuringAFNDataEntity : IoTEntity + [EntityType(EntityTypeEnum.TreeModel)] + public class TreeModelSingleMeasuringEntity : IoTEntity { /// - /// 单项数据对象 + /// 单项数据键值对 /// [SingleMeasuring(nameof(SingleMeasuring))] - public Tuple SingleMeasuring { get; set; } + public required Tuple SingleMeasuring { get; set; } } } diff --git a/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs index 69463ba..0d01f81 100644 --- a/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBOptions.cs @@ -42,5 +42,10 @@ /// 是否使用表模型存储, 默认false,使用tree模型存储 /// public bool UseTableSessionPoolByDefault { get; set; } = false; + + /// + /// 时区,默认为:"UTC+08:00" + /// + public string ZoneId { get; set; } = "UTC+08:00"; } } diff --git a/modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBQueryOptions.cs similarity index 78% rename from modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs rename to modules/JiShe.CollectBus.IoTDB/Options/IoTDBQueryOptions.cs index 90e44b2..bff4641 100644 --- a/modules/JiShe.CollectBus.IoTDB/Options/QueryOptions.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/IoTDBQueryOptions.cs @@ -3,7 +3,7 @@ /// /// 查询条件 /// - public class QueryOptions + public class IoTDBQueryOptions { /// /// 表模型的表名称或者树模型的设备路径 @@ -13,7 +13,7 @@ /// /// 分页 /// - public int Page { get; set; } + public int PageIndex { get; set; } /// /// 分页大小 @@ -23,6 +23,6 @@ /// /// 查询条件 /// - public List Conditions { get; } = new(); + public List Conditions { get; set; } = new(); } } diff --git a/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs b/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs index cf6d3a9..40dd443 100644 --- a/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs +++ b/modules/JiShe.CollectBus.IoTDB/Options/QueryCondition.cs @@ -9,10 +9,17 @@ /// 字段 /// public string Field { get; set; } + /// - /// 操作符 + /// 操作符,>,=,< /// public string Operator { get; set; } + + /// + /// 是否数值,如果是数值,则进行数值比较,否则进行字符串比较 + /// + public bool IsNumber { get; set; } = false; + /// /// 值 /// diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs b/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs index 447f6ce..a093bb7 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/DeviceMetadata.cs @@ -1,4 +1,5 @@ using Apache.IoTDB; +using JiShe.CollectBus.IoTDB.Enums; namespace JiShe.CollectBus.IoTDB.Provider { @@ -7,6 +8,11 @@ namespace JiShe.CollectBus.IoTDB.Provider /// public class DeviceMetadata { + /// + /// IoTDB实体类型枚举 + /// + public EntityTypeEnum EntityType { get; set; } + /// /// 是否有单测量值 /// diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs b/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs index 46ce091..6922c62 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/DevicePathBuilder.cs @@ -1,4 +1,6 @@ -namespace JiShe.CollectBus.IoTDB.Provider +using JiShe.CollectBus.IoTDB.Model; + +namespace JiShe.CollectBus.IoTDB.Provider { /// /// 设备路径构建器 @@ -13,7 +15,7 @@ /// public static string GetDevicePath(T entity) where T : IoTEntity { - return $"root.{entity.SystemName.ToLower()}.`{entity.ProjectCode}`.`{entity.DeviceId}`"; + return $"root.{entity.SystemName.ToLower()}.`{entity.DeviceId}`"; } @@ -28,6 +30,17 @@ var type = typeof(T); return $"{type.Name.ToLower()}"; } + + /// + /// 获取表名称,用作单侧点表模型特殊处理。 + /// + /// + /// + /// + public static string GetDeviceTableName(T entity) where T : IoTEntity + { + return $"{entity.SystemName.ToLower()}.`{entity.DeviceId}`"; + } } } diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs index 7ce03cc..7cfaf32 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/IoTDBProvider.cs @@ -1,15 +1,22 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Reflection; using System.Text; +using System.Threading.Tasks; using Apache.IoTDB; using Apache.IoTDB.DataStructure; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IoTDB.Attribute; using JiShe.CollectBus.IoTDB.Context; using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Model; using JiShe.CollectBus.IoTDB.Options; using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities; namespace JiShe.CollectBus.IoTDB.Provider { @@ -21,7 +28,7 @@ namespace JiShe.CollectBus.IoTDB.Provider private static readonly ConcurrentDictionary MetadataCache = new(); private readonly ILogger _logger; private readonly IIoTDbSessionFactory _sessionFactory; - private readonly IoTDbRuntimeContext _runtimeContext; + private readonly IoTDBRuntimeContext _runtimeContext; private IIoTDbSessionPool CurrentSession => _sessionFactory.GetSessionPool(_runtimeContext.UseTableSessionPool); @@ -35,7 +42,7 @@ namespace JiShe.CollectBus.IoTDB.Provider public IoTDbProvider( ILogger logger, IIoTDbSessionFactory sessionFactory, - IoTDbRuntimeContext runtimeContext) + IoTDBRuntimeContext runtimeContext) { _logger = logger; _sessionFactory = sessionFactory; @@ -51,17 +58,19 @@ namespace JiShe.CollectBus.IoTDB.Provider /// public async Task InsertAsync(T entity) where T : IoTEntity { - var metadata = GetMetadata(); + try + { + var metadata = await GetMetadata(); - var tablet = BuildTablet(new[] { entity }, metadata); + var tablet = BuildTablet(new[] { entity }, metadata); - await CurrentSession.InsertAsync(tablet); - - //int result = await _currentSession.InsertAsync(tablet); - //if (result <= 0) - //{ - // _logger.LogError($"{typeof(T).Name}插入数据没有成功"); - //} + await CurrentSession.InsertAsync(tablet); + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(InsertAsync)} 插入数据时发生异常"); + throw; + } } /// @@ -71,20 +80,51 @@ namespace JiShe.CollectBus.IoTDB.Provider /// public async Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity { - var metadata = GetMetadata(); - - var batchSize = 1000; - var batches = entities.Chunk(batchSize); - - foreach (var batch in batches) + try { - var tablet = BuildTablet(batch, metadata); - await CurrentSession.InsertAsync(tablet); - //var result = await _currentSession.InsertAsync(tablet); - //if (result <= 0) - //{ - // _logger.LogWarning($"{typeof(T).Name} 批量插入数据第{batch}批次没有成功,共{batches}批次。"); - //} + var metadata = await GetMetadata(); + + var batchSize = 1000; + var batches = entities.Chunk(batchSize); + + foreach (var batch in batches) + { + var tablet = BuildTablet(batch, metadata); + await CurrentSession.InsertAsync(tablet); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(BatchInsertAsync)} 批量插入数据时发生异常"); + throw; + } + } + + /// + /// 批量插入数据 + /// + /// + /// 设备元数据 + /// + /// + public async Task BatchInsertAsync(DeviceMetadata deviceMetadata, IEnumerable entities) where T : IoTEntity + { + try + { + + var batchSize = 1000; + var batches = entities.Chunk(batchSize); + + foreach (var batch in batches) + { + var tablet = BuildTablet(batch, deviceMetadata); + await CurrentSession.InsertAsync(tablet); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(BatchInsertAsync)} 批量插入数据时发生异常"); + throw; } } @@ -95,20 +135,54 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - public async Task DeleteAsync(QueryOptions options) where T : IoTEntity + public async Task DeleteAsync(IoTDBQueryOptions options) where T : IoTEntity { - var query = BuildDeleteSQL(options); - var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - - if (!sessionDataSet.HasNext()) + try { - _logger.LogWarning($"{typeof(T).Name} 删除数据时,没有返回受影响记录数量。"); - return 0; - } + var query = await BuildDeleteSQL(options); + var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - //获取唯一结果行 - var row = sessionDataSet.Next(); - return row.Values[0]; + if (!sessionDataSet.HasNext()) + { + _logger.LogWarning($"{typeof(T).Name} 删除数据时,没有返回受影响记录数量。"); + return 0; + } + + //获取唯一结果行 + var row = sessionDataSet.Next(); + return row.Values[0]; + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(DeleteAsync)} 删除数据时发生异常"); + throw; + } + } + + /// + /// 获取设备元数据 + /// + /// + /// + public async Task GetMetadata() where T : IoTEntity + { + var columns = CollectColumnMetadata(typeof(T)); + var metadata = BuildDeviceMetadata(columns); + var metaData = MetadataCache.AddOrUpdate( + typeof(T), + addValueFactory: t => metadata, // 如果键不存在,用此值添加 + updateValueFactory: (t, existingValue) => + { + var columns = CollectColumnMetadata(t); + var metadata = BuildDeviceMetadata(columns); + + //对现有值 existingValue 进行修改,返回新值 + existingValue.ColumnNames = metadata.ColumnNames; + return existingValue; + } + ); + + return await Task.FromResult(metaData); } /// @@ -117,18 +191,32 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() + public async Task> QueryAsync(IoTDBQueryOptions options) where T : IoTEntity, new() { - var query = BuildQuerySQL(options); - var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - - var result = new BusPagedResult + try { - TotalCount = await GetTotalCount(options), - Items = ParseResults(sessionDataSet, options.PageSize) - }; + var query =await BuildQuerySQL(options); + var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query); - return result; + + var result = new BusPagedResult + { + TotalCount = await GetTotalCount(options), + Items = await ParseResults(sessionDataSet, options.PageSize), + PageIndex = options.PageIndex, + PageSize = options.PageSize, + + }; + + result.HasNext = result.TotalCount > 0? result.TotalCount < result.PageSize : false; + + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, $"{nameof(QueryAsync)} IoTDB查询数据时发生异常"); + throw; + } } /// @@ -146,10 +234,39 @@ namespace JiShe.CollectBus.IoTDB.Provider List tempColumnNames = new List(); tempColumnNames.AddRange(metadata.ColumnNames); + var entityTypeAttribute = typeof(T).GetCustomAttribute(); + + if (entityTypeAttribute == null) + { + throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 没有指定,属于异常情况,-101"); + } + + if (metadata.EntityType != entityTypeAttribute.EntityType) + { + throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 {nameof(T)}的EntityType 和{nameof(DeviceMetadata)}的 EntityType 不一致,属于异常情况,-102"); + } + + if (metadata.EntityType == Enums.EntityTypeEnum.TreeModel && _runtimeContext.UseTableSessionPool == true) + { + throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 tree模型不能使用table模型Session连接,属于异常情况,-103"); + } + else if (metadata.EntityType == Enums.EntityTypeEnum.TableModel && _runtimeContext.UseTableSessionPool == false) + { + throw new ArgumentException($"{nameof(BuildTablet)} 构建存储结构{nameof(Tablet)}时 table模型不能使用tree模型Session连接,属于异常情况,-104"); + } + + string tableNameOrTreePath = string.Empty; + var tableNameOrTreePathAttribute = typeof(T).GetCustomAttribute(); + if (tableNameOrTreePathAttribute != null) + { + tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath; + } + foreach (var entity in entities) { timestamps.Add(entity.Timestamps); var rowValues = new List(); + foreach (var measurement in tempColumnNames) { @@ -160,51 +277,79 @@ namespace JiShe.CollectBus.IoTDB.Provider } var value = propertyInfo.GetValue(entity); - if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false) && value != null)//表示当前对象是单测点模式 - { - Type tupleType = value.GetType(); - Type[] tupleArgs = tupleType.GetGenericArguments(); - Type item2Type = tupleArgs[1]; // T 的实际类型 - var item1 = tupleType.GetProperty("Item1")!.GetValue(value); - var item2 = tupleType.GetProperty("Item2")!.GetValue(value); - if (item1 == null || item2 == null) - { - throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,单测点模式构建失败,没有获取测点名称或者测点值,-102。"); - } - - var indexOf = metadata.ColumnNames.IndexOf(measurement); - metadata.ColumnNames[indexOf] = (string)item1!; - - rowValues.Add(item2); - - } - else + if (propertyInfo.IsDefined(typeof(SingleMeasuringAttribute), false) && metadata.IsSingleMeasuring == true)//表示当前对象是单测点模式 { if (value != null) { - rowValues.Add(value); + Type tupleType = value.GetType(); + Type[] tupleArgs = tupleType.GetGenericArguments(); + Type item2Type = tupleArgs[1]; // T 的实际类型 + var item1 = tupleType.GetProperty("Item1")!.GetValue(value); + var item2 = tupleType.GetProperty("Item2")!.GetValue(value); + if (item1 == null || item2 == null) + { + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,单测点模式构建失败,没有获取测点名称或者测点值,-102。"); + } + + var indexOf = metadata.ColumnNames.IndexOf(measurement); + metadata.ColumnNames[indexOf] = (string)item1!; + + rowValues.Add(item2); } else { - //填充默认数据值 - DataTypeDefaultValueMap.TryGetValue(propertyInfo.PropertyType.Name, out object defaultValue); - - rowValues.Add(defaultValue); + rowValues.Add(null); } + + //同时如果是单测点模式,且是table模型存储,路径只能通过DevicePathBuilder.GetDeviceTableName(entity)获取 + if (_runtimeContext.UseTableSessionPool) + { + tableNameOrTreePath = DevicePathBuilder.GetDeviceTableName(entity); + } + } + else + { + + //需要根据value的类型,进行相应的值映射转换,例如datetime转换为long的时间戳值 + if (value != null) + { + Type tupleType = value.GetType(); + var tempValue = tupleType.Name.ToUpper() switch + { + "DATETIME" => Convert.ToDateTime(value).GetDateTimeOffset().ToUnixTimeNanoseconds(), + _ => value + }; + + rowValues.Add(tempValue); + } + else + { + rowValues.Add(value); + } + } } values.Add(rowValues); - if (!_runtimeContext.UseTableSessionPool)//树模型 + //如果指定了路径 + if (!string.IsNullOrWhiteSpace(tableNameOrTreePath)) { - devicePaths.Add(DevicePathBuilder.GetDevicePath(entity)); + devicePaths.Add(tableNameOrTreePath); } else { - devicePaths.Add(DevicePathBuilder.GetTableName()); + if (!_runtimeContext.UseTableSessionPool)//树模型 + { + devicePaths.Add(DevicePathBuilder.GetDevicePath(entity)); + } + else + { + devicePaths.Add(DevicePathBuilder.GetTableName()); + } } + } if (devicePaths.Count > 1) @@ -220,14 +365,15 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 构建tree模型的Tablet /// - /// - /// - /// - /// + /// 已解析的设备数据元数据 + /// 设备路径 + /// 数据集合 + /// 时间戳集合 /// - private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, - List> values, List timestamps) + private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, List> values, List timestamps) { + //todo 树模型需要去掉TAG类型和ATTRIBUTE类型的字段,只需要保留FIELD类型字段即可 + return new Tablet( devicePath, metadata.ColumnNames, @@ -240,16 +386,15 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 构建表模型的Tablet /// - /// - /// - /// - /// + /// 已解析的设备数据元数据 + /// 表名称 + /// 数据集合 + /// 时间戳集合 /// - private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string devicePath, - List> values, List timestamps) + private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string tableName, List> values, List timestamps) { var tablet = new Tablet( - devicePath, + tableName, metadata.ColumnNames, metadata.ColumnCategories, metadata.DataTypes, @@ -266,9 +411,9 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - private string BuildQuerySQL(QueryOptions options) where T : IoTEntity + private async Task BuildQuerySQL(IoTDBQueryOptions options) where T : IoTEntity { - var metadata = GetMetadata(); + var metadata = await GetMetadata(); var sb = new StringBuilder("SELECT "); sb.AppendJoin(", ", metadata.ColumnNames); sb.Append($" FROM {options.TableNameOrTreePath}"); @@ -279,7 +424,7 @@ namespace JiShe.CollectBus.IoTDB.Provider sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition)); } - sb.Append($" LIMIT {options.PageSize} OFFSET {options.Page * options.PageSize}"); + sb.Append($" LIMIT {options.PageSize} OFFSET {options.PageIndex * options.PageSize}"); return sb.ToString(); } @@ -289,9 +434,9 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - private string BuildDeleteSQL(QueryOptions options) where T : IoTEntity + private async Task BuildDeleteSQL(IoTDBQueryOptions options) where T : IoTEntity { - var metadata = GetMetadata(); + var metadata = await GetMetadata(); var sb = new StringBuilder(); if (!_runtimeContext.UseTableSessionPool) @@ -326,10 +471,10 @@ namespace JiShe.CollectBus.IoTDB.Provider { return condition.Operator switch { - ">" => $"{condition.Field} > {condition.Value}", - "<" => $"{condition.Field} < {condition.Value}", - "=" => $"{condition.Field} = '{condition.Value}'", - _ => throw new NotSupportedException($"Operator {condition.Operator} not supported") + ">" => condition.IsNumber ? $"{condition.Field} > {condition.Value}": $"{condition.Field} > '{condition.Value}'", + "<" => condition.IsNumber ? $"{condition.Field} < {condition.Value}" : $"{condition.Field} < '{condition.Value}'", + "=" => condition.IsNumber ? $"{condition.Field} = {condition.Value}" : $"{condition.Field} = '{condition.Value}'", + _ => throw new NotSupportedException($"{nameof(TranslateCondition)} 将查询条件转换为SQL语句时操作符 {condition.Operator} 属于异常情况") }; } @@ -339,7 +484,7 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - private async Task GetTotalCount(QueryOptions options) where T : IoTEntity + private async Task GetTotalCount(IoTDBQueryOptions options) where T : IoTEntity { var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTreePath}"; if (options.Conditions.Any()) @@ -358,10 +503,10 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// /// - private IEnumerable ParseResults(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new() + private async Task> ParseResults(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new() { var results = new List(); - var metadata = GetMetadata(); + var metadata = await GetMetadata(); var properties = typeof(T).GetProperties(); @@ -373,16 +518,24 @@ namespace JiShe.CollectBus.IoTDB.Provider Timestamps = record.Timestamps }; - foreach (var measurement in metadata.ColumnNames) { - var value = record.Values; + int indexOf = metadata.ColumnNames.IndexOf(measurement); + var value = record.Values[indexOf]; var prop = properties.FirstOrDefault(p => p.Name.Equals(measurement, StringComparison.OrdinalIgnoreCase)); if (prop != null) { - typeof(T).GetProperty(measurement)?.SetValue(entity, value); + if (measurement.EndsWith("time")) + { + var tempValue = TimestampHelper.ConvertToDateTime(Convert.ToInt64(value), TimestampUnit.Nanoseconds); + typeof(T).GetProperty(measurement)?.SetValue(entity, value); + } + else + { + typeof(T).GetProperty(measurement)?.SetValue(entity, value); + } } } @@ -392,43 +545,6 @@ namespace JiShe.CollectBus.IoTDB.Provider return results; } - /// - /// 获取设备元数据 - /// - /// - /// - private DeviceMetadata GetMetadata() where T : IoTEntity - { - - var columns = CollectColumnMetadata(typeof(T)); - var metadata = BuildDeviceMetadata(columns); - - return MetadataCache.AddOrUpdate( - typeof(T), - addValueFactory: t => metadata, // 如果键不存在,用此值添加 - updateValueFactory: (t, existingValue) => - { - var columns = CollectColumnMetadata(t); - var metadata = BuildDeviceMetadata(columns); - - //对现有值 existingValue 进行修改,返回新值 - existingValue.ColumnNames = metadata.ColumnNames; - return existingValue; - } - ); - - //return _metadataCache.GetOrAdd(typeof(T), type => - //{ - // var columns = CollectColumnMetadata(type); - // var metadata = BuildDeviceMetadata(columns); - // //if (metadata.IsSingleMeasuring) - // //{ - // // _metadataCache.Remove(typeof(T)); - // //} - // return metadata; - //}); - } - /// /// 获取设备元数据的列 /// @@ -440,21 +556,36 @@ namespace JiShe.CollectBus.IoTDB.Provider foreach (var prop in type.GetProperties()) { + + string typeName = string.Empty; + + Type declaredType = prop.PropertyType; + // 处理可空类型 + if (declaredType.IsGenericType && declaredType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + Type underlyingType = Nullable.GetUnderlyingType(declaredType); + typeName = underlyingType.Name; + } + else + { + typeName = declaredType.Name; + } + //先获取Tag标签和属性标签 ColumnInfo? column = prop.GetCustomAttribute() is not null ? new ColumnInfo( name: prop.Name, category: ColumnCategory.TAG, - dataType: GetDataTypeFromTypeName(prop.PropertyType.Name), + dataType: GetDataTypeFromTypeName(typeName), false ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( prop.Name, ColumnCategory.ATTRIBUTE, - GetDataTypeFromTypeName(prop.PropertyType.Name), + GetDataTypeFromTypeName(typeName), false ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( prop.Name, ColumnCategory.FIELD, - GetDataTypeFromTypeName(prop.PropertyType.Name), + GetDataTypeFromTypeName(typeName), false) : null; @@ -490,9 +621,10 @@ namespace JiShe.CollectBus.IoTDB.Provider /// /// 构建设备元数据 /// - /// + /// 待解析的类 + /// 已处理好的数据列 /// - private DeviceMetadata BuildDeviceMetadata(List columns) + private DeviceMetadata BuildDeviceMetadata(List columns) where T : IoTEntity { var metadata = new DeviceMetadata(); @@ -511,6 +643,15 @@ namespace JiShe.CollectBus.IoTDB.Provider ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata); ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata); + var entityTypeAttribute = typeof(T).GetCustomAttribute(); + + if (entityTypeAttribute == null) + { + throw new ArgumentException($"{nameof(BuildDeviceMetadata)} 构建设备元数据时 {nameof(IoTEntity)} 的EntityType 没有指定,属于异常情况,-101"); + } + + metadata.EntityType = entityTypeAttribute.EntityType; + return metadata; } @@ -592,7 +733,7 @@ namespace JiShe.CollectBus.IoTDB.Provider ["DOUBLE"] = TSDataType.DOUBLE, ["TEXT"] = TSDataType.TEXT, ["NULLTYPE"] = TSDataType.NONE, - ["TIMESTAMP"] = TSDataType.TIMESTAMP, + ["DATETIME"] = TSDataType.TIMESTAMP, ["DATE"] = TSDataType.DATE, ["BLOB"] = TSDataType.BLOB, ["DECIMAL"] = TSDataType.STRING, @@ -612,7 +753,7 @@ namespace JiShe.CollectBus.IoTDB.Provider ["DOUBLE"] = 0.0d, ["TEXT"] = string.Empty, ["NULLTYPE"] = null, - ["TIMESTAMP"] = null, + ["DATETIME"] = null, ["DATE"] = null, ["BLOB"] = null, ["DECIMAL"] = "0.0", diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs b/modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs deleted file mode 100644 index 39a584d..0000000 --- a/modules/JiShe.CollectBus.IoTDB/Provider/IoTEntity.cs +++ /dev/null @@ -1,39 +0,0 @@ -using JiShe.CollectBus.IoTDB.Attribute; - -namespace JiShe.CollectBus.IoTDB.Provider -{ - /// - /// IoT实体基类 - /// - public abstract class IoTEntity - { - /// - /// 系统名称 - /// - [TAGColumn] - public string SystemName { get; set; } - - /// - /// 项目编码 - /// - [TAGColumn] - public string ProjectCode { get; set; } - - /// - /// 设备类型集中器、电表、水表、流量计、传感器等 - /// - [TAGColumn] - public string DeviceType { get; set; } - - /// - /// 设备ID - /// - [TAGColumn] - public string DeviceId { get; set; } - - /// - /// 当前时间戳,单位毫秒 - /// - public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - } -} diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs b/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs index dd04f60..eacf246 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/SessionPoolAdapter.cs @@ -25,6 +25,7 @@ namespace JiShe.CollectBus.IoTDB.Provider .SetNodeUrl(options.ClusterList) .SetUsername(options.UserName) .SetPassword(options.Password) + .SetZoneId(options.ZoneId) .SetFetchSize(options.FetchSize) .SetPoolSize(options.PoolSize) .Build(); @@ -56,7 +57,7 @@ namespace JiShe.CollectBus.IoTDB.Provider var result = await _sessionPool.InsertAlignedTabletAsync(tablet); if (result != 0) { - throw new Exception($"{nameof(TableSessionPoolAdapter)} "); + throw new Exception($"{nameof(SessionPoolAdapter)} Tree模型数据入库没有成功,返回结果为:{result}"); } return result; diff --git a/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs b/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs index be42ad7..137f5a8 100644 --- a/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs +++ b/modules/JiShe.CollectBus.IoTDB/Provider/TableSessionPoolAdapter.cs @@ -25,6 +25,7 @@ namespace JiShe.CollectBus.IoTDB.Provider .SetNodeUrls(options.ClusterList) .SetUsername(options.UserName) .SetPassword(options.Password) + .SetZoneId(options.ZoneId) .SetFetchSize(options.FetchSize) .SetPoolSize(options.PoolSize) .SetDatabase(options.DataBaseName) @@ -54,7 +55,7 @@ namespace JiShe.CollectBus.IoTDB.Provider var result = await _sessionPool.InsertAsync(tablet); if (result != 0) { - throw new Exception($"{nameof(TableSessionPoolAdapter)} "); + throw new Exception($"{nameof(TableSessionPoolAdapter)} table模型数据入库没有成功,返回结果为:{result}"); } return result; diff --git a/modules/JiShe.CollectBus.Kafka/Internal/KafkaOptionConfig.cs b/modules/JiShe.CollectBus.Kafka/Internal/KafkaOptionConfig.cs index 5f7bdf1..3bea5f1 100644 --- a/modules/JiShe.CollectBus.Kafka/Internal/KafkaOptionConfig.cs +++ b/modules/JiShe.CollectBus.Kafka/Internal/KafkaOptionConfig.cs @@ -57,5 +57,5 @@ public class KafkaOptionConfig /// /// 首次采集时间 /// - public DateTime FirstCollectionTime { get; set; } + public DateTime? FirstCollectionTime { get; set; } } \ No newline at end of file diff --git a/modules/JiShe.CollectBus.Kafka/KafkaSubscribeExtensions.cs b/modules/JiShe.CollectBus.Kafka/KafkaSubscribeExtensions.cs index 64f8dec..3c50aae 100644 --- a/modules/JiShe.CollectBus.Kafka/KafkaSubscribeExtensions.cs +++ b/modules/JiShe.CollectBus.Kafka/KafkaSubscribeExtensions.cs @@ -65,7 +65,7 @@ namespace JiShe.CollectBus.Kafka // 实现IKafkaSubscribe接口 var subscribeTypes = assembly.GetTypes().Where(type => typeof(IKafkaSubscribe).IsAssignableFrom(type) && - !type.IsAbstract && !type.IsInterface).ToList(); ; + !type.IsAbstract && !type.IsInterface).ToList(); if (subscribeTypes.Count == 0) continue; diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin_bak.cs similarity index 88% rename from protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs rename to protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin_bak.cs index da59a22..923f9ba 100644 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin.cs +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/BaseProtocolPlugin_bak.cs @@ -17,10 +17,10 @@ using JiShe.CollectBus.FreeRedis; namespace JiShe.CollectBus.Protocol.Contracts.Abstracts { - public abstract class BaseProtocolPlugin : IProtocolPlugin + public abstract class BaseProtocolPlugin_bak //: IProtocolPlugin { private readonly IProducerService _producerService; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IRepository _protocolInfoRepository; private readonly IFreeRedisProvider _redisProvider; @@ -32,12 +32,13 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts public const string errorData = "EE"; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The service provider. - protected BaseProtocolPlugin(IServiceProvider serviceProvider) + protected BaseProtocolPlugin_bak(IServiceProvider serviceProvider) { - _logger = serviceProvider.GetRequiredService>(); + + _logger = serviceProvider.GetRequiredService>(); _protocolInfoRepository = serviceProvider.GetRequiredService>(); _producerService = serviceProvider.GetRequiredService(); _redisProvider = serviceProvider.GetRequiredService(); @@ -607,149 +608,149 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts var time = Appendix.Appendix_A1(hexDatas.Take(6).ToList()); } - /// - /// 通用解析 - /// - /// - /// - /// - public virtual TB3761 AnalyzeReadingDataAsync(MessageReceived messageReceived, - Action? sendAction = null) - { - var hexStringList = messageReceived.MessageHexString.StringToPairs(); - var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); - var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); + ///// + ///// 通用解析 + ///// + ///// + ///// + ///// + //public virtual TB3761 AnalyzeReadingDataAsync(MessageReceived messageReceived, + // Action? sendAction = null) + //{ + // var hexStringList = messageReceived.MessageHexString.StringToPairs(); + // var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); + // var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); - var tb3761 = QGDW3761Config.CommandList.FirstOrDefault(it => it.Afn == afn); - if (tb3761 == null) return null; + // var tb3761 = QGDW3761Config.CommandList.FirstOrDefault(it => it.Afn == afn); + // if (tb3761 == null) return null; - var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); - if (tb3761Fn == null) return null; + // var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); + // if (tb3761Fn == null) return null; - var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); + // var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); - var m = 0; - var rateNumberUpSort = -1; - var rateNumberUp = tb3761Fn.UpList.FirstOrDefault(it => it.Name.Contains("费率数M")); - if (rateNumberUp != null) - { - var rateNumber = analyzeValue.Skip(rateNumberUp.DataIndex).Take(rateNumberUp.DataCount).FirstOrDefault(); - m = Convert.ToInt32(rateNumber); - rateNumberUpSort = rateNumberUp.Sort; - } + // var m = 0; + // var rateNumberUpSort = -1; + // var rateNumberUp = tb3761Fn.UpList.FirstOrDefault(it => it.Name.Contains("费率数M")); + // if (rateNumberUp != null) + // { + // var rateNumber = analyzeValue.Skip(rateNumberUp.DataIndex).Take(rateNumberUp.DataCount).FirstOrDefault(); + // m = Convert.ToInt32(rateNumber); + // rateNumberUpSort = rateNumberUp.Sort; + // } - foreach (var up in tb3761Fn.UpList) - { - var dataIndex = up.DataIndex; - if (dataIndex == 0 && up.Sort > rateNumberUpSort) - { - var sum1 = tb3761Fn.UpList.Where(it => it.Sort < up.Sort) - .Sum(it => it.DataCount); - var sum2 = tb3761Fn.UpList.Where(it => it.Sort < up.Sort && it.Tb3761UpChildlList.Count > 0) - .Sum(it => it.Tb3761UpChildlList.Sum(c=> m * c.DataCount)); - dataIndex = sum1 + sum2; - } + // foreach (var up in tb3761Fn.UpList) + // { + // var dataIndex = up.DataIndex; + // if (dataIndex == 0 && up.Sort > rateNumberUpSort) + // { + // var sum1 = tb3761Fn.UpList.Where(it => it.Sort < up.Sort) + // .Sum(it => it.DataCount); + // var sum2 = tb3761Fn.UpList.Where(it => it.Sort < up.Sort && it.Tb3761UpChildlList.Count > 0) + // .Sum(it => it.Tb3761UpChildlList.Sum(c=> m * c.DataCount)); + // dataIndex = sum1 + sum2; + // } - var value = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, up.DataCount, up.DataType); - if (value != null) - { - up.Value = value.ToString(); - } - if (up.Tb3761UpChildlList.Count > 0) //复费率根据费率数来解析 - { - var repeatCount = m; - foreach (var upChild in up.Tb3761UpChildlList) - { - for (var j = 0; j < repeatCount; j++) - { - var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); - if (val != null) - { - upChild.Name = string.Format(upChild.Name, j + 1); - upChild.Value = val.ToString(); - } - dataIndex += upChild.DataCount; - } - } + // var value = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, up.DataCount, up.DataType); + // if (value != null) + // { + // up.Value = value.ToString(); + // } + // if (up.Tb3761UpChildlList.Count > 0) //复费率根据费率数来解析 + // { + // var repeatCount = m; + // foreach (var upChild in up.Tb3761UpChildlList) + // { + // for (var j = 0; j < repeatCount; j++) + // { + // var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); + // if (val != null) + // { + // upChild.Name = string.Format(upChild.Name, j + 1); + // upChild.Value = val.ToString(); + // } + // dataIndex += upChild.DataCount; + // } + // } - } - } + // } + // } - return tb3761; - } + // return tb3761; + //} - /// - /// 通用解析 日冻结曲线类 - /// - /// - /// - /// - public virtual TB3761 AnalyzeReadingTdcDataAsync(MessageReceived messageReceived, - Action? sendAction = null) - { + ///// + ///// 通用解析 日冻结曲线类 + ///// + ///// + ///// + ///// + //public virtual TB3761 AnalyzeReadingTdcDataAsync(MessageReceived messageReceived, + // Action? sendAction = null) + //{ - var hexStringList = messageReceived.MessageHexString.StringToPairs(); - var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); - var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); + // var hexStringList = messageReceived.MessageHexString.StringToPairs(); + // var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); + // var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); - var tb3761 = QGDW3761Config.CommandTdcList.FirstOrDefault(it => it.Afn == afn); - if (tb3761 == null) return null; + // var tb3761 = QGDW3761Config.CommandTdcList.FirstOrDefault(it => it.Afn == afn); + // if (tb3761 == null) return null; - var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); - if (tb3761Fn == null) return null; + // var tb3761Fn = tb3761.FnList.FirstOrDefault(it => it.Fn == fn); + // if (tb3761Fn == null) return null; - var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); + // var analyzeValue = (List)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data); - foreach (var up in tb3761Fn.UpList) - { - var value = AnalyzeDataAccordingDataType(analyzeValue, up.DataIndex, up.DataCount, up.DataType); - if (value != null) - { - up.Value = value.ToString(); + // foreach (var up in tb3761Fn.UpList) + // { + // var value = AnalyzeDataAccordingDataType(analyzeValue, up.DataIndex, up.DataCount, up.DataType); + // if (value != null) + // { + // up.Value = value.ToString(); - if (up.Tb3761UpChildlList.Count > 0) - { - var dataIndex = up.DataIndex; - var repeatCount = (int)value; - foreach (var upChild in up.Tb3761UpChildlList) - { - for (var j = 0; j < repeatCount; j++) - { - var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); - if (val != null) - { - upChild.Value = val.ToString(); - upChild.Name = string.Format(upChild.Name, j + 1); - } - dataIndex += upChild.DataCount; - } - } - } - } - } + // if (up.Tb3761UpChildlList.Count > 0) + // { + // var dataIndex = up.DataIndex; + // var repeatCount = (int)value; + // foreach (var upChild in up.Tb3761UpChildlList) + // { + // for (var j = 0; j < repeatCount; j++) + // { + // var val = AnalyzeDataAccordingDataType(analyzeValue, dataIndex, upChild.DataCount, upChild.DataType); + // if (val != null) + // { + // upChild.Value = val.ToString(); + // upChild.Name = string.Format(upChild.Name, j + 1); + // } + // dataIndex += upChild.DataCount; + // } + // } + // } + // } + // } - return tb3761; - //var freezeDensity = (FreezeDensity)Convert.ToInt32(hexDatas.Skip(5).Take(1)); - //var addMinute = 0; - //switch (freezeDensity) - //{ - // case FreezeDensity.No:break; - // case FreezeDensity.Min15: - // addMinute = 15; - // break; - // case FreezeDensity.Min30: - // addMinute = 30; - // break; - // case FreezeDensity.Min60: - // addMinute = 60; - // break; - // case FreezeDensity.Min5: break; - // addMinute = 5; - // case FreezeDensity.Min1: - // addMinute = 1; - // break; - // } - } + // return tb3761; + // //var freezeDensity = (FreezeDensity)Convert.ToInt32(hexDatas.Skip(5).Take(1)); + // //var addMinute = 0; + // //switch (freezeDensity) + // //{ + // // case FreezeDensity.No:break; + // // case FreezeDensity.Min15: + // // addMinute = 15; + // // break; + // // case FreezeDensity.Min30: + // // addMinute = 30; + // // break; + // // case FreezeDensity.Min60: + // // addMinute = 60; + // // break; + // // case FreezeDensity.Min5: break; + // // addMinute = 5; + // // case FreezeDensity.Min1: + // // addMinute = 1; + // // break; + // // } + //} private object? AnalyzeDataAccordingDataType(List analyzeValue, int dataIndex,int dataCount,string dataType) { diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/ProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/ProtocolPlugin.cs new file mode 100644 index 0000000..b026366 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/Abstracts/ProtocolPlugin.cs @@ -0,0 +1,374 @@ +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using JiShe.CollectBus.Protocol.Contracts.Models; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; +using Volo.Abp.Domain.Repositories; + +namespace JiShe.CollectBus.Protocol.Contracts.Abstracts +{ + public abstract class ProtocolPlugin:IProtocolPlugin + { + //头部字节长度 + public const int hearderLen = 6; + + public const int tPLen = 6; + + public const string errorData = "EE"; + + private readonly ILogger _logger; + private readonly IRepository _protocolInfoRepository; + public ProtocolPlugin(IServiceProvider serviceProvider, ILogger logger) + { + _logger = logger; + _protocolInfoRepository = serviceProvider.GetRequiredService>(); + + } + + + public abstract ProtocolInfo Info { get; } + + public virtual async Task GetAsync() => await Task.FromResult(Info); + + public virtual async Task AddAsync() + { + if (Info == null) + { + throw new ArgumentNullException(nameof(Info)); + } + + await _protocolInfoRepository.DeleteDirectAsync(a => a.Name == Info.Name); + await _protocolInfoRepository.InsertAsync(Info); + //await _protocolInfoCache.Get() + } + + public abstract Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? receivedAction = null) where T :class; + + + /// + /// 解析376.1帧 + /// + /// + /// + public virtual TB3761? Analysis3761(string messageReceived) + { + try + { + var hexStringList = messageReceived.StringToPairs(); + // 初步校验 + if (hexStringList.Count < 6 || hexStringList.FirstOrDefault() != "68" || hexStringList.Skip(5).Take(1).FirstOrDefault() != "68" || hexStringList.Count < 18 || hexStringList.LastOrDefault() != "16") + { + _logger.LogError($"解析Analysis3761校验不通过,报文:{messageReceived}"); + } + else + { + TB3761 tB3761 = new TB3761 + { + BaseHexMessage = new BaseHexMessage + { + HexMessageString = messageReceived, + HexMessageList = hexStringList + }, + C = Analysis_C(hexStringList), + A = Analysis_A(hexStringList), + AFN_FC = Analysis_AFN_FC(hexStringList), + SEQ = Analysis_SEQ(hexStringList), + UnitData = Analysis_UnitData(hexStringList), + DA = Analysis_DA(hexStringList), + DT = Analysis_DT(hexStringList) + }; + return tB3761; + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis3761错误,报文:{messageReceived},异常:{ex.Message}"); + } + return null; + } + + /// + /// 控制域C解析 + /// + /// + public virtual C? Analysis_C(List hexStringList) + { + try + { + if (hexStringList.Count > 6) + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(6, 1) // 控制域 1字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + if (baseHexMessage.HexMessageList.Count == 0) + return null; + string binStr = baseHexMessage.HexMessageString.HexTo4BinZero(); + C c = new C + { + BaseHexMessage = baseHexMessage, + FC = binStr.Substring(binStr.Length - 4, 4).BinToDec(), + FCV = binStr.Substring(3, 1).BinToDec(), + FCB = binStr.Substring(2, 1).BinToDec(), + PRM = binStr.Substring(1, 1).BinToDec(), + DIR = binStr.Substring(0, 1).BinToDec() + }; + return c; + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_C错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + + return null; + } + + /// + /// 地址域A解析 + /// + /// + /// + public virtual A? Analysis_A(List hexStringList) + { + try + { + if (hexStringList.Count > 7) + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(7, 5) // 地址域 5个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + if (baseHexMessage.HexMessageList.Count == 0) + return null; + A a = new A + { + BaseHexMessage = baseHexMessage, + A1 = baseHexMessage.HexMessageList.ListReverseToStr(0, 2),//.DataConvert(10);//行政区划码A1 + A2 = baseHexMessage.HexMessageList.ListReverseToStr(2, 2).PadLeft(5, '0').HexToDec(),//终端地址A2 + A3 = Analysis_A3(baseHexMessage.HexMessageList) //主站地址和组地址标志A3 + }; + a.Code = $"{a.A1.PadLeft(4, '0')}{a.A2.ToString().PadLeft(5, '0')}"; + return a; + } + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_A错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + + return null; + + } + + /// + /// 站地址和组地址标志A3 + /// + /// 地址域A集合 + /// + public virtual A3? Analysis_A3(List hexAList) + { + try + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexAList.GetRange(4, 1) // 站地址和组地址标志A3 1个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + if (baseHexMessage.HexMessageList.Count == 0) + return null; + var binStr = baseHexMessage.HexMessageString.HexTo4BinZero(); + A3 a3 = new A3 + { + BaseHexMessage = baseHexMessage, + D0 = binStr.Substring(binStr.Length - 1, 1).BinToDec(), + D1_D7 = binStr.Substring(0, binStr.Length - 1).BinToDec() + }; + return a3; + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_A3错误,报文:{string.Join("", hexAList)},异常:{ex.Message}"); + } + + return null; + } + + /// + /// AFN_FC功能码 + /// + /// + public virtual AFN_FC? Analysis_AFN_FC(List hexStringList) + { + try + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(12, 1) //AFN功能码 1个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + if (baseHexMessage.HexMessageList.Count == 0) + return null; + AFN_FC aFN_FC = new AFN_FC + { + BaseHexMessage = baseHexMessage, + AFN = baseHexMessage.HexMessageString.HexToDec(), + }; + return aFN_FC; + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_AFN_FC错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + + return null; + } + + /// + /// 解析帧序列域SEQ + /// + /// + public virtual SEQ? Analysis_SEQ(List hexStringList) + { + try + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(13, 1) //帧序列域 SEQ 1个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + if (baseHexMessage.HexMessageList.Count == 0) + return null; + var binStr = baseHexMessage.HexMessageString.HexTo4BinZero(); + SEQ seq = new SEQ + { + PSEQ = binStr.Substring(binStr.Length - 4, 4).BinToDec(), + CON = binStr.Substring(3, 1).BinToDec(), + FIN = binStr.Substring(2, 1).BinToDec(), + FIR = binStr.Substring(1, 1).BinToDec(), + TpV = binStr.Substring(0, 1).BinToDec() + }; + return seq; + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_SEQ错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + return null; + } + + + /// + /// 数据单元标识及数据单元数据 + /// + public virtual UnitData? Analysis_UnitData(List hexStringList) + { + try + { + + UnitData unitData = new UnitData + { + HexMessageList = hexStringList.GetRange(14, hexStringList.Count - 14 - 2) //总数字节数-固定长度报文头-控制域C-地址域A-校验和CS-结束字符(16H) + }; + unitData.HexMessageString = string.Join("", unitData.HexMessageList); + if (unitData.HexMessageList.Count == 0) + return null; + return unitData; + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_UnitData错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + return null; + } + + /// + /// 信息点DA Pn + /// + /// + public virtual DA? Analysis_DA(List hexStringList) + { + try + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(14, 2) //信息点DA Pn 2个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + if (baseHexMessage.HexMessageList.Count == 0) + return null; + var da1 = baseHexMessage.HexMessageList[0]; + var da2 = baseHexMessage.HexMessageList[1]; + DA da = new DA() + { + BaseHexMessage = baseHexMessage, + Pn = CalculatePn(da1, da2) + }; + return da; + + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_DA错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + return null; + } + + /// + /// 信息类DT Fn + /// + /// + public virtual DT? Analysis_DT(List hexStringList) + { + try + { + BaseHexMessage baseHexMessage = new BaseHexMessage + { + HexMessageList = hexStringList.GetRange(16, 2) //信息类DT Fn 2个字节 + }; + baseHexMessage.HexMessageString = string.Join("", baseHexMessage.HexMessageList); + if (baseHexMessage.HexMessageList.Count == 0) + return null; + var dt1 = baseHexMessage.HexMessageList[0]; + var dt2 = baseHexMessage.HexMessageList[1]; + DT dt = new DT() + { + BaseHexMessage = baseHexMessage, + Fn = CalculateFn(dt1, dt2) + }; + return dt; + + } + catch (Exception ex) + { + _logger.LogError($"解析Analysis_DT错误,报文:{string.Join("", hexStringList)},异常:{ex.Message}"); + } + + return null; + + } + + + /// + /// 计算Pn + /// + /// + /// + /// + public int CalculatePn(string da1, string da2) => (da2.HexToDec() - 1) * 8 + (8 - da1.HexTo4BinZero().IndexOf(da1.Equals("00") ? "0" : "1")); + + + /// + /// 计算Fn + /// + /// + /// + /// + public int CalculateFn(string dt1, string dt2) => dt2.HexToDec() * 8 + (8 - dt1.HexTo4BinZero().IndexOf("1")); + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/AnalysisStrategyContext.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/AnalysisStrategyContext.cs new file mode 100644 index 0000000..8fe8f70 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/AnalysisStrategyContext.cs @@ -0,0 +1,35 @@ +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts +{ + + public class AnalysisStrategyContext + { + private readonly IServiceProvider _provider; + + public AnalysisStrategyContext(IServiceProvider provider) => _provider = provider; + + /// + /// 执行策略 + /// + /// + /// + /// + /// + /// + public Task ExecuteAsync(string type, TInput input) + { + var factory = _provider.GetRequiredService>(); + var strategy = (IAnalysisStrategy)factory(type, typeof(TInput), typeof(TResult)); + return strategy.ExecuteAsync(input); + } + } + + +} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IAnalysisStrategy.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IAnalysisStrategy.cs new file mode 100644 index 0000000..dcd12fb --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IAnalysisStrategy.cs @@ -0,0 +1,15 @@ +using JiShe.CollectBus.Protocol.Contracts.Models; +using JiShe.CollectBus.Protocol.Dto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Contracts.Interfaces +{ + public interface IAnalysisStrategy + { + Task ExecuteAsync(TInput input); + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs index c35dbee..326dad0 100644 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/Interfaces/IProtocolPlugin.cs @@ -14,10 +14,12 @@ namespace JiShe.CollectBus.Protocol.Contracts.Interfaces Task LoadAsync(); - Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) where T : TB3761; + Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? sendAction = null) where T : class; - Task LoginAsync(MessageReceivedLogin messageReceived); + TB3761? Analysis3761(string messageReceived); - Task HeartbeatAsync(MessageReceivedHeartbeat messageReceived); + //Task LoginAsync(MessageReceivedLogin messageReceived); + + //Task HeartbeatAsync(MessageReceivedHeartbeat messageReceived); } } diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs index ecc1f88..a24090a 100644 --- a/protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/Models/TB3761.cs @@ -1,5 +1,4 @@ -using JiShe.CollectBus.Common.Enums; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -7,49 +6,243 @@ using System.Threading.Tasks; namespace JiShe.CollectBus.Protocol.Contracts.Models { + + /// + /// 解析3761报文 + /// public class TB3761 { - public int Id { get; set; } + /// + /// 报文 + /// + public BaseHexMessage? BaseHexMessage { get; set;} - public AFN Afn { get; set; } + /// + /// 控制域C + /// + public C? C { get; set; } - public List FnList { get; set; } + /// + /// 地址域A + /// + public A? A { get; set; } + + /// + /// 帧序列域 SEQ + /// + public SEQ? SEQ { get; set; } + + /// + /// 用户数据区 + /// 功能码 + /// + public AFN_FC? AFN_FC { get; set; } + + /// + /// 用户数据区 + /// 信息点DA Pn + /// + public DA? DA { get; set; } + + /// + /// 用户数据区 + /// 信息类DT Fn + /// + public DT? DT { get; set; } + + /// + /// 数据单元标识和数据单元格式 + /// + public UnitData? UnitData { get; set; } + } + + #region + + /// + /// 报文信息 + /// + public class BaseHexMessage + { + /// + /// 报文 + /// + public string? HexMessageString { get; set; } + + /// + /// 报文数组 + /// + public List? HexMessageList { get; set; } + } + + /// + /// 控制域C + /// + public class C + { + /// + /// 控制域C报文 + /// + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 传输方向位D7 DIR=0,表示此帧报文是由主站发出的下行报文;DIR=1,表示此帧报文是由终端发出的上行报文。 + /// + public int DIR { get; set; } + /// + /// D6启动标志位 0:表示此帧报文来自从动站(终端),1:表示此帧报文来自启动站(服务端) + /// + public int PRM { get; set; } + /// + /// D5下行:帧计数位(FCB)/上行(ACD):要求访问位(终端有重要事件等待访问), + /// + public int FCB { get; set; } + /// + /// 下行:帧计数有效位(决定FCB位有效/无效)/上行:保留 D4 + /// + public int FCV { get; set; } + /// + /// 功能码 D0-D3 + /// + public int FC { get; set; } } - public class TB3761FN - { - public int Id { get; set; } + /// + /// 地址域A + /// + public class A + { + /// + /// 地址域报文 + /// + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 集中器/终端编码 + /// + public string? Code { get; set; } + + /// + /// 行政区划码A1 + /// + public string? A1 { get; set; } + /// + /// 终端地址A2 + /// + public int A2 { get; set; } + /// + /// 站地址和组地址标志A3 + /// + public A3? A3 { get; set; } + } + + /// + /// 站地址和组地址标志A3 + /// + public class A3 + { + /// + /// 地址域A3报文 + /// + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 终端组地址标志,D0=0即False 表示终端地址A2 为单地址 + /// + public int D0 { get; set; } + /// + /// 主站地址 MSA D1~D7 组成 0~127 + /// + public int D1_D7 { get; set; } + } + + + /// + /// 帧序列域 SEQ + /// + public class SEQ + { + /// + /// 帧序列域报文 + /// + public BaseHexMessage? BaseHexMessage { get; set; } + /// + /// 响应帧序号 + /// + public int PSEQ { get; set; } + /// + /// CON为“1”,表示需要对该帧报文进行确认;置“0”,表示不需要对该帧报文进行确认。 + /// + public int CON { get; set; } + /// + /// 末帧标志 + /// + public int FIN { get; set; } + /// + /// 首帧标志 + /// + public int FIR { get; set; } + /// + /// 帧时间标签有效位,TpV=0,表示在附加信息域中无时间标签Tp;TpV=1,表示在附加信息域中带有时间标签Tp + /// + public int TpV { get; set; } + } + + + + /// + /// 用户数据区 + /// 功能码 + /// + public class AFN_FC + { + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 功能码 + /// + public int AFN { get; set; } + } + + + /// + /// 用户数据区 + /// 信息点DA Pn + /// + public class DA + { + public BaseHexMessage? BaseHexMessage { get; set; } + + /// + /// 信息点 DA Pn + /// + public int Pn { get; set; } + } + + + /// + /// 用户数据区 + /// 信息类DT Fn + /// + public class DT + { + public BaseHexMessage? BaseHexMessage { get; set; } + /// + /// 信息类 DT Fn + /// public int Fn { get; set; } - - public string Text { get; set; } - - public List UpList { get; set; } - } - public class TB3761UP + + /// + /// 数据单元标识和数据单元格式 + /// + public class UnitData: BaseHexMessage { - public int Id { get; set; } - public string Name { get; set; } - - public string? Value { get; set; } - - public string DataType { get; set; } - - public int DataIndex { get; set; } - - //public int DataIndex { get; set; } - - public int DataCount { get; set; } - - //public int ParentId { get; set; } - - public int Sort { get; set; } - - public List Tb3761UpChildlList { get; set; } } + #endregion + } diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Protocol/Dto/AFN0_F1_AnalysisDto.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Protocol/Dto/AFN0_F1_AnalysisDto.cs new file mode 100644 index 0000000..d6ab85a --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/Protocol/Dto/AFN0_F1_AnalysisDto.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Dto +{ + public class AFN0_F1_AnalysisDto: UnitDataDto + { + + } +} diff --git a/protocols/JiShe.CollectBus.Protocol.Contracts/Protocol/Dto/UnitDataDto.cs b/protocols/JiShe.CollectBus.Protocol.Contracts/Protocol/Dto/UnitDataDto.cs new file mode 100644 index 0000000..cd59203 --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol.Contracts/Protocol/Dto/UnitDataDto.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.Dto +{ + public class UnitDataDto + { + /// + /// 集中器地址 + /// + public string? Code { get; set; } + + /// + /// AFN功能码 + /// + public int AFN { get; set; } + + /// + /// 信息点 + /// + public int Pn { get; set; } + + /// + /// 信息类 + /// + public int Fn { get; set; } + + /// + /// 数据时标(最近数据时间点的时间),如:8:00 08:15 记录08:15 + /// + public string? DataTime { get; set; } + + /// + /// 密度(分) + /// + public int TimeDensity { get; set; } + } + +} diff --git a/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs index dbdfa82..e291aa0 100644 --- a/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs +++ b/protocols/JiShe.CollectBus.Protocol.Test/TestProtocolPlugin.cs @@ -4,23 +4,25 @@ using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Protocols; using JiShe.CollectBus.Protocol.Contracts.Abstracts; -using NUglify.JavaScript.Syntax; +using Microsoft.Extensions.Logging; +using TouchSocket.Sockets; namespace JiShe.CollectBus.Protocol.Test { - public class TestProtocolPlugin : BaseProtocolPlugin + public class TestProtocolPlugin : ProtocolPlugin { + private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The service provider. - public TestProtocolPlugin(IServiceProvider serviceProvider) : base(serviceProvider) + public TestProtocolPlugin(IServiceProvider serviceProvider, ILogger logger) : base(serviceProvider, logger) { } public sealed override ProtocolInfo Info => new(nameof(TestProtocolPlugin), "Test", "TCP", "Test协议", "DTS1980-Test"); - public override Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) + public override Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? receivedAction = null) { throw new NotImplementedException(); } diff --git a/protocols/JiShe.CollectBus.Protocol/AnalysisData/AFN_00H/AFN0_F1_Analysis.cs b/protocols/JiShe.CollectBus.Protocol/AnalysisData/AFN_00H/AFN0_F1_Analysis.cs new file mode 100644 index 0000000..f5ba2af --- /dev/null +++ b/protocols/JiShe.CollectBus.Protocol/AnalysisData/AFN_00H/AFN0_F1_Analysis.cs @@ -0,0 +1,45 @@ +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using JiShe.CollectBus.Protocol.Contracts.Models; +using JiShe.CollectBus.Protocol.Dto; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Protocol.AnalysisData.AFN_00H +{ + public class AFN0_F1_Analysis: IAnalysisStrategy + { + private readonly ILogger _logger; + + public AFN0_F1_Analysis(ILogger logger) + { + _logger = logger; + } + + public Task ExecuteAsync(TB3761 tB3761) + { + try + { + ArgumentNullException.ThrowIfNull(nameof(tB3761)); + AFN0_F1_AnalysisDto dto = new AFN0_F1_AnalysisDto + { + Code = tB3761.A?.Code, + AFN = tB3761.AFN_FC?.AFN ?? 0, + Fn = tB3761.DT?.Fn ?? 0, + Pn = tB3761.DA?.Pn ?? 0 + }; + return Task.FromResult(dto); + } + catch (Exception ex) + { + _logger.LogError(ex, $"00_1解析失败:{tB3761.A?.Code}-{tB3761.DT?.Fn ?? 0}-{tB3761?.BaseHexMessage?.HexMessageString},{ex.Message}"); + return null; + } + } + } +} diff --git a/protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs b/protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs index b0275a5..8019d34 100644 --- a/protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs +++ b/protocols/JiShe.CollectBus.Protocol/JiSheCollectBusProtocolModule.cs @@ -1,5 +1,14 @@ -using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.Protocol.AnalysisData; +using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Contracts.Abstracts; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Serilog.Core; +using System.Reflection; +using TouchSocket.Core; using Volo.Abp; using Volo.Abp.Modularity; @@ -10,6 +19,7 @@ namespace JiShe.CollectBus.Protocol public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddKeyedSingleton(nameof(StandardProtocolPlugin)); + RegisterProtocolAnalysis(context.Services); } public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) @@ -17,5 +27,66 @@ namespace JiShe.CollectBus.Protocol var standardProtocol = context.ServiceProvider.GetRequiredKeyedService(nameof(StandardProtocolPlugin)); await standardProtocol.LoadAsync(); } + + public void RegisterProtocolAnalysis(IServiceCollection services) + { + // 扫描并注册所有策略 + var strategyMetadata = new Dictionary<(string, Type, Type), Type>(); + services.AddTransient(); + + // 批量注册 + var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + if (string.IsNullOrWhiteSpace(assemblyPath)) + { + return; + } + var dllFiles = Directory.GetFiles(Path.Combine(assemblyPath, "Plugins") , "*.dll"); + foreach (var file in dllFiles) + { + // 跳过已加载的程序集 + var assemblyName = AssemblyName.GetAssemblyName(file); + var existingAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().FullName == assemblyName.FullName); + var assembly = existingAssembly ?? Assembly.LoadFrom(file); + // 实现IAnalysisStrategy接口 + var analysisStrategyTypes = assembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAnalysisStrategy<,>))); + if (analysisStrategyTypes.Count() == 0) + continue; + foreach (var analysisStrategyType in analysisStrategyTypes) + { + // 通过反射获取静态元数据 + var strategyType = analysisStrategyType.Name; + var genericArgs = analysisStrategyType.GetInterface("IAnalysisStrategy`2")!.GetGenericArguments(); + var inputType = genericArgs[0]; + var resultType = genericArgs[1]; + // 注册策略实现 + services.AddTransient(analysisStrategyType); + strategyMetadata[(strategyType, inputType, resultType)] = analysisStrategyType; + + + } + } + + // 注册元数据字典 + services.AddSingleton(strategyMetadata); + + // 注册策略解析工厂 + services.AddTransient>(provider => (name, inputType, resultType) => + { + var metadata = provider.GetRequiredService>(); + if (metadata.TryGetValue((name, inputType, resultType), out var strategyType)) + { + return provider.GetRequiredService(strategyType); + } + else + { + var logger= provider.GetRequiredService>(); + logger.LogWarning($"未能找到解析策略:{name}-{inputType}-{resultType}"); + return null; + } + }); + + + } } } diff --git a/protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs b/protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs index a28cd2d..6a5e3a5 100644 --- a/protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs +++ b/protocols/JiShe.CollectBus.Protocol/StandardProtocolPlugin.cs @@ -1,62 +1,243 @@ -using JiShe.CollectBus.Common.Enums; +using DeviceDetectorNET.Parser.Device; +using JiShe.CollectBus.Common.BuildSendDatas; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.Enums; +using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.IotSystems.Protocols; +using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts.Abstracts; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; using JiShe.CollectBus.Protocol.Contracts.Models; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; +using TouchSocket.Sockets; +using Volo.Abp.Domain.Repositories; namespace JiShe.CollectBus.Protocol { - public class StandardProtocolPlugin : BaseProtocolPlugin + public class StandardProtocolPlugin : ProtocolPlugin { + private readonly ILogger _logger; + + private readonly IProducerService _producerService; + + private readonly IRepository _deviceRepository; /// /// Initializes a new instance of the class. /// /// The service provider. - public StandardProtocolPlugin(IServiceProvider serviceProvider) : base(serviceProvider) + public StandardProtocolPlugin(IServiceProvider serviceProvider,ILogger logger) : base(serviceProvider, logger) { + _logger= logger; + //_logger = serviceProvider.GetRequiredService>(); + _producerService = serviceProvider.GetRequiredService(); + _deviceRepository = serviceProvider.GetRequiredService>(); } public sealed override ProtocolInfo Info => new(nameof(StandardProtocolPlugin), "376.1", "TCP", "376.1协议", "DTS1980"); - public override async Task AnalyzeAsync(MessageReceived messageReceived, Action? sendAction = null) + public override async Task AnalyzeAsync(ITcpSessionClient client, string messageReceived, Action? sendAction = null) { - var hexStringList = messageReceived.MessageHexString.StringToPairs(); - var aTuple = (Tuple)hexStringList.GetAnalyzeValue(CommandChunkEnum.A); - var afn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); - var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); - - T analyze = default; - - switch ((AFN)afn) + TB3761? tB3761 = Analysis3761(messageReceived); + if (tB3761 != null) { - case AFN.确认或否认: - AnalyzeAnswerDataAsync(messageReceived, sendAction); - break; - case AFN.设置参数: break; - case AFN.查询参数: break; - case AFN.请求实时数据: - if (Enum.IsDefined(typeof(ATypeOfDataItems), fn)) + if (tB3761.AFN_FC?.AFN == (int)AFN.链路接口检测) + { + if (tB3761.A == null || tB3761.A.Code.IsNullOrWhiteSpace() || tB3761.A.A3?.D1_D7 == null || tB3761.SEQ?.PSEQ == null) { - analyze = (T?)AnalyzeReadingDataAsync(messageReceived, sendAction); + _logger.LogError($"解析AFN.链路接口检测报文失败,报文:{messageReceived},TB3761:{tB3761.Serialize()}"); } - break; - case AFN.请求历史数据: - if (Enum.IsDefined(typeof(IIdataTypeItems), fn)) + else { - analyze = (T?)AnalyzeReadingTdcDataAsync(messageReceived, sendAction); + if (tB3761.DT?.Fn == (int)FN.登录) + { + // 登录回复 + if (tB3761.SEQ.CON == (int)CON.需要对该帧进行确认) + await LoginAsync(client, messageReceived, tB3761.A.Code, tB3761.A.A3?.D1_D7, tB3761.SEQ?.PSEQ); + } + else if (tB3761.DT?.Fn == (int)FN.心跳) + { + // 心跳回复 + //心跳帧有两种情况: + //1. 集中器先有登录帧,再有心跳帧 + //2. 集中器没有登录帧,只有心跳帧 + await HeartbeatAsync(client, messageReceived, tB3761.A.Code, tB3761.A.A3?.D1_D7, tB3761.SEQ?.PSEQ); + } } - break; - case AFN.数据转发: - AnalyzeTransparentForwardingAnswerAsync(messageReceived, sendAction); - break; + + } + + } + return (tB3761 as T)!; + } + + + /// + /// 登录回复 + /// + /// + /// + /// + /// + /// + public async Task LoginAsync(ITcpSessionClient client,string messageReceived, string code, int? msa, int? pseq) + { + string oldClientId = $"{client.Id}"; + await client.ResetIdAsync(code); + var deviceInfoList = await _deviceRepository.GetListAsync(a => a.Number == code); + if (deviceInfoList != null && deviceInfoList.Count > 1) + { + //todo 推送集中器编号重复预警 + _logger.LogError($"集中器编号:{code},存在多个集中器,请检查集中器编号是否重复"); + return; } - return await Task.FromResult(analyze); + var entity = deviceInfoList?.FirstOrDefault(a => a.Number == code); + if (entity == null) + { + await _deviceRepository.InsertAsync(new Device(code, oldClientId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); + } + else + { + entity.UpdateByLoginAndHeartbeat(oldClientId); + await _deviceRepository.UpdateAsync(entity); + } + + var messageReceivedLoginEvent = new MessageReceivedLogin + { + ClientId = code, + ClientIp = client.IP, + ClientPort = client.Port, + MessageHexString = messageReceived, + DeviceNo = code, + MessageId = Guid.NewGuid().ToString() + }; + + //await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); + + + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); + + //await _producerBus.Publish( messageReceivedLoginEvent); + + //var aTuple = (Tuple)messageReceived.StringToPairs().GetAnalyzeValue(CommandChunkEnum.A); + //var seq = (Seq)messageReceived.StringToPairs().GetAnalyzeValue(CommandChunkEnum.SEQ); + var reqParam = new ReqParameter2 + { + AFN = AFN.确认或否认, + FunCode = (int)CFromStationFunCode.链路数据, + PRM = PRM.从动站报文, + A =code, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.需要对该帧进行确认, + PRSEQ = pseq!.Value + }, + MSA = msa!.Value, + Pn = 0, + Fn = 1 + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); + //await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); + + await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceivedLoginEvent.ClientId, DeviceNo = messageReceivedLoginEvent.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceivedLoginEvent.MessageId }); + //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }); } + /// + /// 心跳帧解析 + /// + /// + /// + /// + /// + /// + public async Task HeartbeatAsync(ITcpSessionClient client,string messageReceived, string code, int? msa, int? pseq) + { + + string clientId = code; + string oldClientId = $"{client.Id}"; + var deviceInfoList = await _deviceRepository.GetListAsync(a => a.Number == code); + if (deviceInfoList != null && deviceInfoList.Count > 1) + { + //todo 推送集中器编号重复预警 + _logger.LogError($"集中器编号:{code},存在多个集中器,请检查集中器编号是否重复"); + return; + } + + var entity = deviceInfoList?.FirstOrDefault(a => a.Number == code); + if (entity == null) //没有登录帧的设备,只有心跳帧 + { + await client.ResetIdAsync(clientId); + await _deviceRepository.InsertAsync(new Device(code, oldClientId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); + } + else + { + if (clientId != oldClientId) + { + entity.UpdateByLoginAndHeartbeat(oldClientId); + } + else + { + entity.UpdateByLoginAndHeartbeat(); + } + + await _deviceRepository.UpdateAsync(entity); + } + + var messageReceivedHeartbeatEvent = new MessageReceivedHeartbeat + { + ClientId = clientId, + ClientIp = client.IP, + ClientPort = client.Port, + MessageHexString = messageReceived, + DeviceNo = code, + MessageId = Guid.NewGuid().ToString() + }; + //await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); + + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); + //await _producerBus.Publish(messageReceivedHeartbeatEvent); + + var reqParam = new ReqParameter2() + { + AFN = AFN.确认或否认, + FunCode = (int)CFromStationFunCode.链路数据, + PRM = PRM.从动站报文, + A = code, + Seq = new Seq() + { + TpV = TpV.附加信息域中无时间标签, + FIRFIN = FIRFIN.单帧, + CON = CON.不需要对该帧进行确认, + PRSEQ = pseq!.Value, + }, + MSA = msa!.Value, + Pn = 0, + Fn = 1 + }; + var bytes = Build3761SendData.BuildSendCommandBytes(reqParam); + //await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + + await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceivedHeartbeatEvent.ClientId, DeviceNo = messageReceivedHeartbeatEvent.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceivedHeartbeatEvent.MessageId }); + + //await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }); + + } + + + + + + #region 上行命令 //68 diff --git a/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs b/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs index 5a99bf1..5d2ba4b 100644 --- a/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs +++ b/services/JiShe.CollectBus.Application/Plugins/TcpMonitor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -14,6 +15,10 @@ using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageReceiveds; using JiShe.CollectBus.Kafka.Producer; using JiShe.CollectBus.Protocol.Contracts; +using JiShe.CollectBus.Protocol.Contracts.Abstracts; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using JiShe.CollectBus.Protocol.Contracts.Models; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using TouchSocket.Core; using TouchSocket.Sockets; @@ -21,6 +26,7 @@ using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; +using static System.Formats.Asn1.AsnWriter; using static FreeSql.Internal.GlobalFilter; namespace JiShe.CollectBus.Plugins @@ -31,65 +37,51 @@ namespace JiShe.CollectBus.Plugins private readonly ILogger _logger; private readonly IRepository _deviceRepository; private readonly IDistributedCache _ammeterInfoCache; + private readonly IServiceProvider _serviceProvider; - /// - /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// + /// public TcpMonitor(IProducerService producerService, ILogger logger, IRepository deviceRepository, - IDistributedCache ammeterInfoCache) + IDistributedCache ammeterInfoCache, IServiceProvider serviceProvider) { _producerService = producerService; _logger = logger; _deviceRepository = deviceRepository; _ammeterInfoCache = ammeterInfoCache; + _serviceProvider= serviceProvider; + + } public async Task OnTcpReceived(ITcpSession client, ReceivedDataEventArgs e) { var messageHexString = Convert.ToHexString(e.ByteBlock.Span); - var hexStringList = messageHexString.StringToPairs(); - var aFn = (int?)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN); - var fn = (int?)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN); - var aTuple = (Tuple)hexStringList.GetAnalyzeValue(CommandChunkEnum.A); - if (aFn.HasValue && fn.HasValue && aTuple != null && !string.IsNullOrWhiteSpace(aTuple.Item1)) - { - var tcpSessionClient = (ITcpSessionClient)client; - if ((AFN)aFn == AFN.链路接口检测) - { - switch (fn) - { - case 1: - await OnTcpLoginReceived(tcpSessionClient, messageHexString, aTuple.Item1); - break; - case 3: - //心跳帧有两种情况: - //1. 集中器先有登录帧,再有心跳帧 - //2. 集中器没有登录帧,只有心跳帧 - await OnTcpHeartbeatReceived(tcpSessionClient, messageHexString, aTuple.Item1); - break; - default: - _logger.LogError($"指令初步解析失败,指令内容:{messageHexString}"); - break; - } - } - else - { - await OnTcpNormalReceived(tcpSessionClient, messageHexString, aTuple.Item1,aFn.ToString()!.PadLeft(2,'0')); - } + var tcpSessionClient = (ITcpSessionClient)client; + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) + { + _logger.LogError("协议不存在!"); } - else + + TB3761? tB3761 = await protocolPlugin!.AnalyzeAsync(tcpSessionClient, messageHexString); + if (tB3761 == null) { _logger.LogError($"指令初步解析失败,指令内容:{messageHexString}"); } - - await e.InvokeNext(); + else + { + await OnTcpNormalReceived(tcpSessionClient, messageHexString, tB3761); + } + await e.InvokeNext(); } //[GeneratorPlugin(typeof(ITcpConnectingPlugin))] @@ -130,114 +122,22 @@ namespace JiShe.CollectBus.Plugins await e.InvokeNext(); } - /// - /// 登录帧处理 - /// - /// - /// - /// 集中器编号 - /// - private async Task OnTcpLoginReceived(ITcpSessionClient client, string messageHexString, string deviceNo) - { - string oldClientId = $"{client.Id}"; - - await client.ResetIdAsync(deviceNo); - - var deviceInfoList= await _deviceRepository.GetListAsync(a => a.Number == deviceNo); - if (deviceInfoList != null && deviceInfoList.Count > 1) - { - //todo 推送集中器编号重复预警 - _logger.LogError($"集中器编号:{deviceNo},存在多个集中器,请检查集中器编号是否重复"); - return; - } - - var entity = deviceInfoList?.FirstOrDefault(a => a.Number == deviceNo); - if (entity == null) - { - await _deviceRepository.InsertAsync(new Device(deviceNo, oldClientId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); - } - else - { - entity.UpdateByLoginAndHeartbeat(oldClientId); - await _deviceRepository.UpdateAsync(entity); - } - - var messageReceivedLoginEvent = new MessageReceivedLogin - { - ClientId = deviceNo, - ClientIp = client.IP, - ClientPort = client.Port, - MessageHexString = messageHexString, - DeviceNo = deviceNo, - MessageId = Guid.NewGuid().ToString() - }; - - //await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); - - - await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent); - - //await _producerBus.Publish( messageReceivedLoginEvent); - } - - private async Task OnTcpHeartbeatReceived(ITcpSessionClient client, string messageHexString, string deviceNo) - { - string clientId = deviceNo; - string oldClientId = $"{client.Id}"; - - var deviceInfoList = await _deviceRepository.GetListAsync(a => a.Number == deviceNo); - if (deviceInfoList != null && deviceInfoList.Count > 1) - { - //todo 推送集中器编号重复预警 - _logger.LogError($"集中器编号:{deviceNo},存在多个集中器,请检查集中器编号是否重复"); - return; - } - - var entity = deviceInfoList?.FirstOrDefault(a => a.Number == deviceNo); - if (entity == null) //没有登录帧的设备,只有心跳帧 - { - await client.ResetIdAsync(clientId); - await _deviceRepository.InsertAsync(new Device(deviceNo, oldClientId, DateTime.Now, DateTime.Now, DeviceStatus.Online)); - } - else - { - if (clientId != oldClientId) - { - entity.UpdateByLoginAndHeartbeat(oldClientId); - } - else - { - entity.UpdateByLoginAndHeartbeat(); - } - - await _deviceRepository.UpdateAsync(entity); - } - - var messageReceivedHeartbeatEvent = new MessageReceivedHeartbeat - { - ClientId = clientId, - ClientIp = client.IP, - ClientPort = client.Port, - MessageHexString = messageHexString, - DeviceNo = deviceNo, - MessageId = Guid.NewGuid().ToString() - }; - //await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); - - await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent); - //await _producerBus.Publish(messageReceivedHeartbeatEvent); - } - /// /// 正常帧处理,将不同的AFN进行分发 /// - /// + /// /// - /// - /// + /// /// - private async Task OnTcpNormalReceived(ITcpSessionClient client, string messageHexString, string deviceNo,string aFn) + private async Task OnTcpNormalReceived(ITcpSessionClient tcpSessionClient,string messageHexString, TB3761? tB3761) { + //var _protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + //if (_protocolPlugin == null) + //{ + // _logger.LogError("376.1协议插件不存在!"); + //} + + //await _producerBus.Publish(new MessageReceived //{ // ClientId = client.Id, @@ -260,15 +160,45 @@ namespace JiShe.CollectBus.Plugins // DeviceNo = deviceNo, // MessageId = NewId.NextGuid().ToString() //}); - await _producerService.ProduceAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + + if(tB3761?.AFN_FC?.AFN==null || tB3761.DT?.Fn==null) { - ClientId = client.Id, - ClientIp = client.IP, - ClientPort = client.Port, - MessageHexString = messageHexString, - DeviceNo = deviceNo, - MessageId = Guid.NewGuid().ToString() - }); + _logger.LogError("376.1协议解析AFN失败"); + return; + } + // 登录心跳已做了处理,故需要忽略登录和心跳帧 + //if(tB3761.DT?.Fn == (int)FN.登录 || tB3761.DT?.Fn == (int)FN.心跳) + // return; + + //TODO:根据AFN进行分流推送到kafka + string topicName = string.Format(ProtocolConst.AFNTopicNameFormat, tB3761?.AFN_FC?.AFN.ToString().PadLeft(2,'0')); + + List topics = ProtocolConstExtensions.GetAllTopicNamesByReceived(); + + if(topics.Contains(topicName)) + await _producerService.ProduceAsync(topicName, new MessageReceived + { + ClientId = tcpSessionClient.Id, + ClientIp = tcpSessionClient.IP, + ClientPort = tcpSessionClient.Port, + MessageHexString = messageHexString, + DeviceNo = tB3761?.A?.Code!, + MessageId = Guid.NewGuid().ToString() + }); + else + { + _logger.LogError($"不支持的上报kafka主题:{topicName}"); + await _producerService.ProduceAsync(ProtocolConst.SubscriberReceivedEventName, new MessageReceived + { + ClientId = tcpSessionClient.Id, + ClientIp = tcpSessionClient.IP, + ClientPort = tcpSessionClient.Port, + MessageHexString = messageHexString, + DeviceNo = tB3761?.A?.Code!, + MessageId = Guid.NewGuid().ToString() + }); + } + } } } diff --git a/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs b/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs index 1a056a7..625c1e6 100644 --- a/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs +++ b/services/JiShe.CollectBus.Application/RedisDataCache/RedisDataCacheService.cs @@ -42,6 +42,8 @@ namespace JiShe.CollectBus.RedisDataCache Instance = _freeRedisProvider.Instance; } + //todo 单个数据查询 + /// /// 单个添加数据 /// diff --git a/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs index 2f22697..86582af 100644 --- a/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/services/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -1,33 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Apache.IoTDB.DataStructure; -using Apache.IoTDB; -using Confluent.Kafka; -using JiShe.CollectBus.Ammeters; -using JiShe.CollectBus.FreeSql; -using JiShe.CollectBus.IotSystems.PrepayModel; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Logging; -using JiShe.CollectBus.IotSystems.AFNEntity; -using JiShe.CollectBus.Protocol.Contracts.Interfaces; -using Microsoft.Extensions.DependencyInjection; -using JiShe.CollectBus.Common.Consts; -using JiShe.CollectBus.Common.Enums; -using System.Diagnostics.Metrics; -using JiShe.CollectBus.Common.DeviceBalanceControl; -using JiShe.CollectBus.Kafka.Attributes; -using System.Text.Json; +using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Application.Contracts; -using JiShe.CollectBus.Common.Models; -using System.Diagnostics; +using JiShe.CollectBus.Common.Consts; +using JiShe.CollectBus.Common.DeviceBalanceControl; +using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; +using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.IoTDB.Context; using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Model; using JiShe.CollectBus.IoTDB.Options; +using JiShe.CollectBus.IoTDB.Provider; +using JiShe.CollectBus.IotSystems.PrepayModel; +using JiShe.CollectBus.Kafka.Attributes; using JiShe.CollectBus.Kafka.Internal; -using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Protocol.Contracts.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; namespace JiShe.CollectBus.Samples; @@ -35,12 +31,12 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS { private readonly ILogger _logger; private readonly IIoTDbProvider _iotDBProvider; - private readonly IoTDbRuntimeContext _dbContext; + private readonly IoTDBRuntimeContext _dbContext; private readonly IoTDbOptions _options; private readonly IRedisDataCacheService _redisDataCacheService; public SampleAppService(IIoTDbProvider iotDBProvider, IOptions options, - IoTDbRuntimeContext dbContext, ILogger logger, IRedisDataCacheService redisDataCacheService) + IoTDBRuntimeContext dbContext, ILogger logger, IRedisDataCacheService redisDataCacheService) { _iotDBProvider = iotDBProvider; _options = options.Value; @@ -52,33 +48,23 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS /// /// 测试 UseSessionPool /// - /// + /// /// [HttpGet] - public async Task UseSessionPool(long timestamps) + public async Task UseSessionPool(long testTime) { - string? messageHexString = null; - if (timestamps == 0) - { - timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - _logger.LogError($"timestamps_{timestamps}"); - } - else - { - messageHexString = messageHexString + timestamps; - } - ElectricityMeter meter = new ElectricityMeter() + ElectricityMeterTreeModel meter = new ElectricityMeterTreeModel() { SystemName = "energy", - DeviceId = "402440506", + DeviceId = "402440506s", DeviceType = "Ammeter", Current = 10, MeterModel = "DDZY-1980", - ProjectCode = "10059", + ProjectId = "10059", Voltage = 10, - IssuedMessageHexString = messageHexString, - Timestamps = timestamps, + IssuedMessageHexString = "messageHexString", + Timestamps = testTime// DateTimeOffset.UtcNow.ToUnixTimeNanoseconds()//testTime.GetDateTimeOffset().ToUnixTimeNanoseconds(), }; await _iotDBProvider.InsertAsync(meter); } @@ -88,18 +74,19 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS /// /// [HttpGet] - public async Task UseTableSessionPool() + public async Task UseTableSessionPool(DateTime time) { - ElectricityMeter meter2 = new ElectricityMeter() + var testTime = time; + ElectricityMeterTreeModel meter2 = new ElectricityMeterTreeModel() { SystemName = "energy", DeviceId = "402440506", DeviceType = "Ammeter", Current = 10, MeterModel = "DDZY-1980", - ProjectCode = "10059", + ProjectId = "10059", Voltage = 10, - Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), }; await _iotDBProvider.InsertAsync(meter2); @@ -113,11 +100,142 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS DeviceType = "Ammeter", Current = 10, MeterModel = "DDZY-1980", - ProjectCode = "10059", + ProjectId = "10059", Voltage = 10, - Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), }; await _iotDBProvider.InsertAsync(meter); + + } + + /// + /// 测试Session切换3 + /// + /// + [HttpGet] + public async Task UseTableSessionPool3(DateTime time) + { + var testTime = time; + ElectricityMeterTreeModel meter2 = new ElectricityMeterTreeModel() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + Current = 10, + MeterModel = "DDZY-1980", + ProjectId = "10059", + Voltage = 10, + IssuedMessageHexString = "dsdfsfd", + Timestamps = DateTimeOffset.UtcNow.ToUnixTimeNanoseconds(), + + }; + + await _iotDBProvider.InsertAsync(meter2); + + _dbContext.UseTableSessionPool = true; + + + ElectricityMeter meter3 = new ElectricityMeter() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + Current = 10, + MeterModel = "DDZY-1980", + ProjectId = "10059", + Voltage = 10, + Currentd = 22, + IssuedMessageHexString = "dsdfsfd", + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), + }; + + //var dd = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + //var dd3 = DateTimeOffset.Now.ToUnixTimeMicroseconds(); + //var dd2 = DateTimeOffset.Now.ToUnixTimeNanoseconds(); + + await _iotDBProvider.InsertAsync(meter3); + } + + /// + /// 测试树模型单个测点数据项 + /// + /// + /// + [HttpGet] + public async Task TestTreeModelSingleMeasuringEntity(string measuring, string value, DateTime time) + { + var meter = new TreeModelSingleMeasuringEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + ProjectId = "10059", + Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleMeasuring = new Tuple(measuring, value) + }; + await _iotDBProvider.InsertAsync(meter); + } + + /// + /// 测试树模型单个测点数据项2 + /// + /// + /// + [HttpGet] + public async Task TestTreeModelSingleMeasuringEntity2(string measuring, int value, DateTime time) + { + var meter = new TreeModelSingleMeasuringEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + ProjectId = "10059", + Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleMeasuring = new Tuple(measuring, value) + }; + await _iotDBProvider.InsertAsync(meter); + } + + /// + /// 测试表模型单个测点数据项 + /// + /// + /// + [HttpGet] + public async Task TestTableModelSingleMeasuringEntity(string measuring, string value, DateTime time) + { + var meter = new TableModelSingleMeasuringEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + ProjectId = "10059", + Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleColumn = new Tuple(measuring, value) + }; + _dbContext.UseTableSessionPool = true; + await _iotDBProvider.InsertAsync(meter); + } + + /// + /// 测试表模型单个测点数据项2 + /// + /// + /// + [HttpGet] + public async Task TestTableModelSingleMeasuringEntity2(string measuring, int value, DateTime time) + { + var meter = new TableModelSingleMeasuringEntity() + { + SystemName = "energy", + DeviceId = "402440506", + DeviceType = "Ammeter", + ProjectId = "10059", + Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(), + SingleColumn = new Tuple(measuring, value) + }; + _dbContext.UseTableSessionPool = true; + await _iotDBProvider.InsertAsync(meter); } /// @@ -170,27 +288,7 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS await Task.CompletedTask; } - - - /// - /// 测试单个测点数据项 - /// - /// - /// - [HttpGet] - public async Task TestSingleMeasuringAFNData(string measuring, string value) - { - var meter = new SingleMeasuringAFNDataEntity() - { - SystemName = "energy", - DeviceId = "402440506", - DeviceType = "Ammeter", - ProjectCode = "10059", - Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - SingleMeasuring = new Tuple(measuring, value) - }; - await _iotDBProvider.InsertAsync(meter); - } + /// /// 测试Redis批量读取10万条数据性能 diff --git a/services/JiShe.CollectBus.Application/Samples/TestAppService.cs b/services/JiShe.CollectBus.Application/Samples/TestAppService.cs index 7b0500a..8241adb 100644 --- a/services/JiShe.CollectBus.Application/Samples/TestAppService.cs +++ b/services/JiShe.CollectBus.Application/Samples/TestAppService.cs @@ -11,8 +11,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; -using JiShe.CollectBus.Common.Helpers; -using JiShe.CollectBus.IotSystems.AFNEntity; +using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Protocol.Contracts.Interfaces; using Microsoft.Extensions.DependencyInjection; using JiShe.CollectBus.Cassandra; diff --git a/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs b/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs index 4ff5dd6..0d97c69 100644 --- a/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs +++ b/services/JiShe.CollectBus.Application/ScheduledMeterReading/BasicScheduledMeterReadingService.cs @@ -1,4 +1,6 @@ -using DnsClient.Protocol; +using Confluent.Kafka; +using DnsClient.Protocol; +using FreeSql; using JiShe.CollectBus.Ammeters; using JiShe.CollectBus.Application.Contracts; using JiShe.CollectBus.Common.BuildSendDatas; @@ -9,7 +11,11 @@ using JiShe.CollectBus.Common.Extensions; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.Common.Models; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IoTDB.Context; using JiShe.CollectBus.IoTDB.Interface; +using JiShe.CollectBus.IoTDB.Model; +using JiShe.CollectBus.IoTDB.Options; +using JiShe.CollectBus.IoTDB.Provider; using JiShe.CollectBus.IotSystems.MessageIssueds; using JiShe.CollectBus.IotSystems.MeterReadingRecords; using JiShe.CollectBus.IotSystems.Watermeter; @@ -19,6 +25,8 @@ using JiShe.CollectBus.Protocol.Contracts; using JiShe.CollectBus.RedisDataCache; using JiShe.CollectBus.Repository.MeterReadingRecord; using Mapster; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; @@ -38,22 +46,22 @@ namespace JiShe.CollectBus.ScheduledMeterReading { private readonly ILogger _logger; private readonly IIoTDbProvider _dbProvider; - private readonly IMeterReadingRecordRepository _meterReadingRecordRepository; private readonly IProducerService _producerService; private readonly IRedisDataCacheService _redisDataCacheService; private readonly KafkaOptionConfig _kafkaOptions; + private readonly IoTDBRuntimeContext _runtimeContext; public BasicScheduledMeterReadingService( ILogger logger, - IMeterReadingRecordRepository meterReadingRecordRepository, IProducerService producerService, IRedisDataCacheService redisDataCacheService, IIoTDbProvider dbProvider, + IoTDBRuntimeContext runtimeContext, IOptions kafkaOptions) { _logger = logger; _dbProvider = dbProvider; - _meterReadingRecordRepository = meterReadingRecordRepository; + _runtimeContext = runtimeContext; _producerService = producerService; _redisDataCacheService = redisDataCacheService; _kafkaOptions = kafkaOptions.Value; @@ -133,17 +141,23 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (meteryType == MeterTypeEnum.Ammeter.ToString()) { - //_ = AmmerterCreatePublishTask(timeDensity, $"{tasksToBeIssueModel.NextTaskTime:yyyyMMddHHmm00}"); - + //List pushTaskInfos = new(); + _runtimeContext.UseTableSessionPool = true; + var metadata = await _dbProvider.GetMetadata(); _ = CreateMeterPublishTask( timeDensity: timeDensity, - taskBatch: $"{tasksToBeIssueModel.NextTaskTime:yyyyMMddHHmm00}", + nextTaskTime: tasksToBeIssueModel.NextTaskTime.CalculateNextCollectionTime(timeDensity), meterType: MeterTypeEnum.Ammeter, - taskCreateAction: (timeDensity, data, groupIndex, taskBatch) => + taskCreateAction: (timeDensity, data, groupIndex, timestamps) => { - AmmerterCreatePublishTaskAction(timeDensity, data, groupIndex, taskBatch); + var tempTask = AmmerterCreatePublishTaskAction(timeDensity, data, groupIndex, timestamps); + if (tempTask == null || tempTask.Count <= 0) + { + _logger.LogWarning($"{data.Name} 任务数据构建失败:{data.Serialize()}"); + return; + } + _dbProvider.BatchInsertAsync(metadata, tempTask); }); - } else if (meteryType == MeterTypeEnum.WaterMeter.ToString()) { @@ -152,7 +166,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading _ = CreateMeterPublishTask( timeDensity: timeDensity, - taskBatch: $"{tasksToBeIssueModel.NextTaskTime:yyyyMMddHHmm00}", + nextTaskTime: tasksToBeIssueModel.NextTaskTime, meterType: MeterTypeEnum.Ammeter, taskCreateAction: (timeDensity, data, groupIndex, taskBatch) => { @@ -169,6 +183,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading //根据当前的采集频率和类型,重新更新下一个任务点,把任务的创建源固定在当前逻辑,避免任务处理的逻辑异常导致任务创建失败。 + tasksToBeIssueModel.LastTaskTime = tasksToBeIssueModel.NextTaskTime; tasksToBeIssueModel.NextTaskTime = tasksToBeIssueModel.NextTaskTime.CalculateNextCollectionTime(timeDensity); await FreeRedisProvider.Instance.SetAsync(item, tasksToBeIssueModel); } @@ -193,11 +208,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// public virtual async Task InitAmmeterCacheData(string gatherCode = "") { -#if DEBUG - return; - - - +#if DEBUG var timeDensity = "15"; var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, "JiSheCollectBus2", MeterTypeEnum.Ammeter, timeDensity)}"; @@ -236,9 +247,9 @@ namespace JiShe.CollectBus.ScheduledMeterReading timer1.Stop(); - _logger.LogError($"读取数据更花费时间{timer1.ElapsedMilliseconds}毫秒"); - //DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); - //return; + _logger.LogError($"读取数据总共花费时间{timer1.ElapsedMilliseconds}毫秒"); + DeviceGroupBalanceControl.InitializeCache(focusAddressDataLista, _kafkaOptions.NumPartitions); + return; #else var meterInfos = await GetAmmeterInfoList(gatherCode); #endif @@ -261,13 +272,18 @@ namespace JiShe.CollectBus.ScheduledMeterReading //根据采集频率分组,获得采集频率分组 var meterInfoGroupByTimeDensity = meterInfos.GroupBy(d => d.TimeDensity); + if (_kafkaOptions.FirstCollectionTime.HasValue == false) + { + _kafkaOptions.FirstCollectionTime = DateTime.Now; + } //先处理采集频率任务缓存 foreach (var item in meterInfoGroupByTimeDensity) { TasksToBeIssueModel nextTask = new TasksToBeIssueModel() { + LastTaskTime = null, TimeDensity = item.Key, - NextTaskTime = _kafkaOptions.FirstCollectionTime.CalculateNextCollectionTime(item.Key),//使用首次采集时间作为下一次采集时间 + NextTaskTime = _kafkaOptions.FirstCollectionTime.Value.CalculateNextCollectionTime(item.Key),//使用首次采集时间作为下一次采集时间 }; //todo 首次采集时间节点到目前运行时间中漏采的时间点,可以考虑使用IoTDB的存储,利用时间序列处理。 @@ -395,13 +411,13 @@ namespace JiShe.CollectBus.ScheduledMeterReading }; var taskBatch = $"{currentTime:yyyyMMddHHmm00}"; - Parallel.For(0, _kafkaOptions.NumPartitions, options, async groupIndex => - { - var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + //Parallel.For(0, _kafkaOptions.NumPartitions, options, async groupIndex => + //{ + // var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + // var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - _ = CreateMeterKafkaTaskMessage(redisCacheTelemetryPacketInfoHashKey, redisCacheTelemetryPacketInfoZSetScoresIndexKey); - }); + // _ = CreateMeterKafkaTaskMessage(redisCacheTelemetryPacketInfoHashKey, redisCacheTelemetryPacketInfoZSetScoresIndexKey); + //}); await Task.CompletedTask; @@ -426,13 +442,13 @@ namespace JiShe.CollectBus.ScheduledMeterReading }; var taskBatch = $"{currentTime:yyyyMMddHHmm00}"; - Parallel.For(0, _kafkaOptions.NumPartitions, options, async groupIndex => - { - var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + //Parallel.For(0, _kafkaOptions.NumPartitions, options, async groupIndex => + //{ + // var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + // var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - _ = CreateMeterKafkaTaskMessage(redisCacheTelemetryPacketInfoHashKey, redisCacheTelemetryPacketInfoZSetScoresIndexKey); - }); + // _ = CreateMeterKafkaTaskMessage(redisCacheTelemetryPacketInfoHashKey, redisCacheTelemetryPacketInfoZSetScoresIndexKey); + //}); } /// @@ -443,8 +459,18 @@ namespace JiShe.CollectBus.ScheduledMeterReading { //获取缓存中的电表信息 int timeDensity = 15; - var currentTime = DateTime.Now; + //var currentTime = DateTime.Now.CalculateNextCollectionTime(timeDensity); + var currentTime = Convert.ToDateTime("2025-04-21 17:42:00").CalculateNextCollectionTime(timeDensity); + var redisCacheKey = string.Format(RedisConst.CacheTasksToBeIssuedKey, SystemType, ServerTagName,MeterTypeEnum.Ammeter,timeDensity); + var taskInfo = await FreeRedisProvider.Instance.GetAsync(redisCacheKey); + + if (taskInfo == null || taskInfo.LastTaskTime.HasValue == false) + { + _logger.LogError($"{nameof(AmmeterScheduledMeterFifteenMinuteReading)} {timeDensity}分钟获取任务下发信息失败,请检查Redis中是否有对应的任务下发信息"); + return; + } + // 自动计算最佳并发度 int recommendedThreads = DeviceGroupBalanceControl.CalculateOptimalThreadCount(); @@ -452,73 +478,81 @@ namespace JiShe.CollectBus.ScheduledMeterReading { MaxDegreeOfParallelism = recommendedThreads, }; - var taskBatch = $"{currentTime:yyyyMMddHHmm00}"; + var pendingCopyReadTime = taskInfo.LastTaskTime.Value.GetDateTimeOffset().ToUnixTimeNanoseconds(); - Parallel.For(0, _kafkaOptions.NumPartitions, options, async groupIndex => + var conditions = new List(); + conditions.Add(new QueryCondition() { - var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - - _ = CreateMeterKafkaTaskMessage(redisCacheTelemetryPacketInfoHashKey, redisCacheTelemetryPacketInfoZSetScoresIndexKey); + Field = "PendingCopyReadTime", + Operator = "=", + IsNumber = true, + Value = pendingCopyReadTime }); + _ = CreateMeterKafkaTaskMessage(timeDensity, new IoTDBQueryOptions() + { + TableNameOrTreePath = DevicePathBuilder.GetTableName(), + PageIndex = 1, + PageSize = 3000, + Conditions = conditions, + }); } - /// - /// 创建电表待发送的任务数据 - /// - /// 采集频率 - /// 时间格式的任务批次名称 - /// - private async Task AmmerterCreatePublishTask(int timeDensity, string taskBatch) - { - var timer = Stopwatch.StartNew(); + ///// + ///// 创建电表待发送的任务数据 + ///// + ///// 采集频率 + ///// 时间格式的任务批次名称 + ///// + //private async Task AmmerterCreatePublishTask(int timeDensity, string taskBatch) + //{ + // var timer = Stopwatch.StartNew(); - //获取对应频率中的所有电表信息 - var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; - var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; - var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + // //获取对应频率中的所有电表信息 + // var redisCacheMeterInfoHashKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + // var redisCacheMeterInfoSetIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; + // var redisCacheMeterInfoZSetScoresIndexKeyTemp = $"{string.Format(RedisConst.CacheMeterInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity)}"; - List meterInfos = new List(); - decimal? cursor = null; - string member = null; - bool hasNext; - do - { - var page = await _redisDataCacheService.GetAllPagedData( - redisCacheMeterInfoHashKeyTemp, - redisCacheMeterInfoZSetScoresIndexKeyTemp, - pageSize: 1000, - lastScore: cursor, - lastMember: member); + // List meterInfos = new List(); + // decimal? cursor = null; + // string member = null; + // bool hasNext; + // do + // { + // var page = await _redisDataCacheService.GetAllPagedData( + // redisCacheMeterInfoHashKeyTemp, + // redisCacheMeterInfoZSetScoresIndexKeyTemp, + // pageSize: 1000, + // lastScore: cursor, + // lastMember: member); - meterInfos.AddRange(page.Items); - cursor = page.HasNext ? page.NextScore : null; - member = page.HasNext ? page.NextMember : null; - hasNext = page.HasNext; - } while (hasNext); + // meterInfos.AddRange(page.Items); + // cursor = page.HasNext ? page.NextScore : null; + // member = page.HasNext ? page.NextMember : null; + // hasNext = page.HasNext; + // } while (hasNext); - if (meterInfos == null || meterInfos.Count <= 0) - { - timer.Stop(); - _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); - return; - } + // if (meterInfos == null || meterInfos.Count <= 0) + // { + // timer.Stop(); + // _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} {timeDensity}分钟采集待下发任务创建失败,没有获取到缓存信息,-105"); + // return; + // } - await DeviceGroupBalanceControl.ProcessWithThrottleAsync( - items: meterInfos, - deviceIdSelector: data => data.FocusAddress, - processor: (data, groupIndex) => - { - AmmerterCreatePublishTaskAction(timeDensity, data, groupIndex, taskBatch); - } - ); + // await DeviceGroupBalanceControl.ProcessWithThrottleAsync( + // items: meterInfos, + // deviceIdSelector: data => data.FocusAddress, + // processor: (data, groupIndex) => + // { + // AmmerterCreatePublishTaskAction(timeDensity, data, groupIndex, taskBatch); + // } + // ); - timer.Stop(); - _logger.LogInformation($"{nameof(AmmerterCreatePublishTaskAction)} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},总共{meterInfos.Count}表计信息"); - } + // timer.Stop(); + // _logger.LogInformation($"{nameof(AmmerterCreatePublishTaskAction)} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},总共{meterInfos.Count}表计信息"); + //} /// @@ -527,38 +561,33 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// 采集频率 /// 电表信息 /// 集中器所在分组 - /// 时间格式的任务批次名称 + /// 采集频率对应的时间戳 /// - private void AmmerterCreatePublishTaskAction(int timeDensity - , AmmeterInfo ammeterInfo, int groupIndex, string taskBatch) + private List AmmerterCreatePublishTaskAction(int timeDensity + , AmmeterInfo ammeterInfo, int groupIndex, DateTime timestamps) { + var currentTime = DateTime.Now; + var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; //todo 检查需要待补抄的电表的时间点信息,保存到需要待补抄的缓存中。如果此线程异常,该如何补偿? - var currentTime = DateTime.Now; - var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); - - var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - var redisCacheTelemetryPacketInfoSetIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - if (string.IsNullOrWhiteSpace(ammeterInfo.ItemCodes)) { // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,采集项为空,-101"); - return; + return null; } //载波的不处理 if (ammeterInfo.MeteringPort == (int)MeterLinkProtocolEnum.Carrierwave) { //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}数据采集指令生成失败,载波不处理,-102"); - return; + return null; } if (ammeterInfo.State.Equals(2)) { //_logger.LogWarning($"{nameof(AmmerterCreatePublishTask)} {ammeterInfo.Name} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}状态为禁用,不处理"); - return; + return null; } ////排除1天未在线的集中器生成指令 或 排除集中器配置为自动上报的集中器 @@ -571,22 +600,22 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (string.IsNullOrWhiteSpace(ammeterInfo.AreaCode)) { // _logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信区号为空"); - return; + return null; } if (string.IsNullOrWhiteSpace(ammeterInfo.Address)) { //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址为空"); - return; + return null; } if (Convert.ToInt32(ammeterInfo.Address) > 65535) { //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},集中器通信地址无效,确保大于65535"); - return; + return null; } if (ammeterInfo.MeteringCode <= 0 || ammeterInfo.MeteringCode > 33) { //_logger.LogError($"{nameof(AmmerterCreatePublishTask)} 表ID:{ammeterInfo.ID},非有效测量点号({ammeterInfo.MeteringCode})"); - return; + return null; } List tempCodes = ammeterInfo.ItemCodes.Deserialize>()!; @@ -613,7 +642,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading if (tempSubCodes == null || tempSubCodes.Count <= 0) { //_logger.LogInformation($"{nameof(AmmerterCreatePublishTask)} 集中器{ammeterInfo.FocusAddress}的电表{ammeterInfo.Name}自动上报数据主动采集1类数据时数据类型为空"); - return; + return null; } else { @@ -683,18 +712,18 @@ namespace JiShe.CollectBus.ScheduledMeterReading var meterReadingRecords = new MeterReadingTelemetryPacketInfo() { - ProjectID = ammeterInfo.ProjectID, + SystemName = SystemType, + ProjectId = $"{ammeterInfo.ProjectID}", + DeviceType = $"{MeterTypeEnum.Ammeter}", + DeviceId = $"{ammeterInfo.FocusAddress}", + Timestamps = DateTimeOffset.Now.ToUnixTimeNanoseconds(), DatabaseBusiID = ammeterInfo.DatabaseBusiID, - PendingCopyReadTime = pendingCopyReadTime, + PendingCopyReadTime = timestamps, CreationTime = currentTime, MeterAddress = ammeterInfo.AmmerterAddress, - MeterId = ammeterInfo.MeterId, - MeterType = MeterTypeEnum.Ammeter, - FocusAddress = ammeterInfo.FocusAddress, - FocusId = ammeterInfo.FocusId, - AFN = aFN, + AFN = (int)aFN, Fn = fn, - Seq = builderResponse.Seq, + //Seq = builderResponse.Seq, MSA = builderResponse.MSA, ItemCode = tempItem, TaskMark = CommonHelper.GetTaskMark((int)aFN, fn, ammeterInfo.MeteringCode, builderResponse.MSA), @@ -709,37 +738,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading taskList.Add(meterReadingRecords); } - if (taskList == null - || taskList.Count() <= 0 - || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoHashKey) - || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoSetIndexKey) - || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoZSetScoresIndexKey)) - { - _logger.LogError($"{nameof(AmmerterCreatePublishTaskAction)} {ammeterInfo.Name}的写入参数异常,{redisCacheTelemetryPacketInfoHashKey}:{redisCacheTelemetryPacketInfoSetIndexKey}:{redisCacheTelemetryPacketInfoZSetScoresIndexKey},-101"); - return; - } - - using (var pipe = FreeRedisProvider.Instance.StartPipe()) - { - foreach (var item in taskList) - { - // 主数据存储Hash - pipe.HSet(redisCacheTelemetryPacketInfoHashKey, item.MemberId, item.Serialize()); - - // Set索引缓存 - pipe.SAdd(redisCacheTelemetryPacketInfoSetIndexKey, item.MemberId); - - // ZSET索引缓存Key - pipe.ZAdd(redisCacheTelemetryPacketInfoZSetScoresIndexKey, item.ScoreValue, item.MemberId); - } - pipe.EndPipe(); - } - - //await _redisDataCacheService.BatchInsertDataAsync( - // redisCacheTelemetryPacketInfoHashKey, - // redisCacheTelemetryPacketInfoSetIndexKey, - // redisCacheTelemetryPacketInfoZSetScoresIndexKey, - // taskList); + return taskList; } #endregion @@ -864,7 +863,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading } if (meterTaskInfosList != null && meterTaskInfosList.Count > 0) { - await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); + // await _meterReadingRecordRepository.InsertManyAsync(meterTaskInfosList); } ////删除任务数据 @@ -877,52 +876,52 @@ namespace JiShe.CollectBus.ScheduledMeterReading _logger.LogInformation($"{nameof(WatermeterScheduledMeterAutoReading)} {timeDensity}分钟采集水表数据处理完成"); } - /// - /// 创建水表待发送的任务数据 - /// - /// 采集频率 - /// 水表信息 - /// 集中器所在分组 - /// 时间格式的任务批次名称 - /// - private void WatermeterCreatePublishTaskAction(int timeDensity - , WatermeterInfo meterInfo, int groupIndex, string taskBatch) - { - var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; + ///// + ///// 创建水表待发送的任务数据 + ///// + ///// 采集频率 + ///// 水表信息 + ///// 集中器所在分组 + ///// 时间格式的任务批次名称 + ///// + //private void WatermeterCreatePublishTaskAction(int timeDensity + // , WatermeterInfo meterInfo, int groupIndex, string taskBatch) + //{ + // var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary; - var currentTime = DateTime.Now; - var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); + // var currentTime = DateTime.Now; + // var pendingCopyReadTime = currentTime.AddMinutes(timeDensity); - var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - var redisCacheTelemetryPacketInfoSetIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + // var redisCacheTelemetryPacketInfoHashKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoHashKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + // var redisCacheTelemetryPacketInfoSetIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoSetIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; + // var redisCacheTelemetryPacketInfoZSetScoresIndexKey = $"{string.Format(RedisConst.CacheTelemetryPacketInfoZSetScoresIndexKey, SystemType, ServerTagName, MeterTypeEnum.Ammeter, timeDensity, groupIndex, taskBatch)}"; - var taskInfo = new MeterReadingTelemetryPacketInfo() - { - Seq= null, - - }; - // + // var taskInfo = new MeterReadingTelemetryPacketInfo() + // { + // Seq= null, - Build188SendData.Build188WaterMeterReadingSendDataUnit(meterInfo.Address); + // }; + // // - using (var pipe = FreeRedisProvider.Instance.StartPipe()) - { - // 主数据存储Hash - pipe.HSet(redisCacheTelemetryPacketInfoHashKey, taskInfo.MemberId, taskInfo.Serialize()); + // Build188SendData.Build188WaterMeterReadingSendDataUnit(meterInfo.Address); - // Set索引缓存 - pipe.SAdd(redisCacheTelemetryPacketInfoSetIndexKey, taskInfo.MemberId); + // using (var pipe = FreeRedisProvider.Instance.StartPipe()) + // { + // // 主数据存储Hash + // pipe.HSet(redisCacheTelemetryPacketInfoHashKey, taskInfo.MemberId, taskInfo.Serialize()); - // ZSET索引缓存Key - pipe.ZAdd(redisCacheTelemetryPacketInfoZSetScoresIndexKey, taskInfo.ScoreValue, taskInfo.MemberId); + // // Set索引缓存 + // pipe.SAdd(redisCacheTelemetryPacketInfoSetIndexKey, taskInfo.MemberId); - pipe.EndPipe(); - } + // // ZSET索引缓存Key + // pipe.ZAdd(redisCacheTelemetryPacketInfoZSetScoresIndexKey, taskInfo.ScoreValue, taskInfo.MemberId); - } + // pipe.EndPipe(); + // } + + //} #endregion @@ -961,11 +960,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// 创建表的待发送的任务数据 /// /// 采集频率 - /// 时间格式的任务批次名称 + /// 采集频率对应的任务时间戳 /// 表类型 /// 具体的创建任务的委托 /// - private async Task CreateMeterPublishTask(int timeDensity, string taskBatch, MeterTypeEnum meterType, Action taskCreateAction) where T : DeviceCacheBasicModel + private async Task CreateMeterPublishTask(int timeDensity, DateTime nextTaskTime, MeterTypeEnum meterType, Action taskCreateAction) where T : DeviceCacheBasicModel { var timer = Stopwatch.StartNew(); @@ -978,20 +977,29 @@ namespace JiShe.CollectBus.ScheduledMeterReading decimal? cursor = null; string member = null; bool hasNext; - do - { - var page = await _redisDataCacheService.GetAllPagedData( - redisCacheMeterInfoHashKeyTemp, - redisCacheMeterInfoZSetScoresIndexKeyTemp, - pageSize: 1000, - lastScore: cursor, - lastMember: member); + //do + //{ + // var page = await _redisDataCacheService.GetAllPagedData( + // redisCacheMeterInfoHashKeyTemp, + // redisCacheMeterInfoZSetScoresIndexKeyTemp, + // pageSize: 1000, + // lastScore: cursor, + // lastMember: member); - meterInfos.AddRange(page.Items); - cursor = page.HasNext ? page.NextScore : null; - member = page.HasNext ? page.NextMember : null; - hasNext = page.HasNext; - } while (hasNext); + // meterInfos.AddRange(page.Items); + // cursor = page.HasNext ? page.NextScore : null; + // member = page.HasNext ? page.NextMember : null; + // hasNext = page.HasNext; + //} while (hasNext); + + + var page = await _redisDataCacheService.GetAllPagedData( + redisCacheMeterInfoHashKeyTemp, + redisCacheMeterInfoZSetScoresIndexKeyTemp, + pageSize: 10, + lastScore: cursor, + lastMember: member); + meterInfos.AddRange(page.Items); if (meterInfos == null || meterInfos.Count <= 0) { @@ -1000,56 +1008,40 @@ namespace JiShe.CollectBus.ScheduledMeterReading return; } - await DeviceGroupBalanceControl.ProcessWithThrottleAsync( items: meterInfos, deviceIdSelector: data => data.FocusAddress, processor: (data, groupIndex) => { - taskCreateAction(timeDensity, data, groupIndex, taskBatch); + taskCreateAction(timeDensity, data, groupIndex, nextTaskTime); } ); timer.Stop(); - _logger.LogInformation($"{nameof(CreateMeterPublishTask)} {meterType} {timeDensity}分钟采集待下发任务创建完成,{timer.ElapsedMilliseconds},总共{meterInfos.Count}表计信息"); + _logger.LogInformation($"{nameof(CreateMeterPublishTask)} {meterType} {timeDensity}分钟采集待下发任务创建完成,耗时{timer.ElapsedMilliseconds}毫秒,总共{meterInfos.Count}表计信息"); } /// /// 创建Kafka消息 /// - /// - /// /// - private async Task CreateMeterKafkaTaskMessage( - string redisCacheTelemetryPacketInfoHashKey, - string redisCacheTelemetryPacketInfoZSetScoresIndexKey) + private async Task CreateMeterKafkaTaskMessage(int timeDensity, IoTDBQueryOptions options) where T : IoTEntity, new() { - if (string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoHashKey) || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoHashKey)) - { - throw new Exception($"{nameof(CreateMeterKafkaTaskMessage)} 创建Kafka消息失败,参数异常,-101"); - } - - decimal? cursor = null; - string member = null; + int pageNumber = 0; bool hasNext; var stopwatch = Stopwatch.StartNew(); do { - var page = await _redisDataCacheService.GetAllPagedData( - redisCacheTelemetryPacketInfoHashKey, - redisCacheTelemetryPacketInfoZSetScoresIndexKey, - pageSize: 1000, - lastScore: cursor, - lastMember: member); + options.PageIndex = pageNumber++; - cursor = page.HasNext ? page.NextScore : null; - member = page.HasNext ? page.NextMember : null; - hasNext = page.HasNext; + var pageResult = await _dbProvider.QueryAsync(options); - await DeviceGroupBalanceControl.ProcessWithThrottleAsync( - items: page.Items, - deviceIdSelector: data => data.FocusAddress, + hasNext = pageResult.HasNext; + + await DeviceGroupBalanceControl.ProcessWithThrottleAsync( + items: pageResult.Items.ToList(), + deviceIdSelector: data => data.DeviceId, processor: (data, groupIndex) => { _ = KafkaProducerIssuedMessageAction(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, data, groupIndex); @@ -1059,9 +1051,57 @@ namespace JiShe.CollectBus.ScheduledMeterReading } while (hasNext); stopwatch.Stop(); - _logger.LogError($"{nameof(CreateMeterKafkaTaskMessage)} {redisCacheTelemetryPacketInfoHashKey}采集推送完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); + _logger.LogError($"{nameof(CreateMeterKafkaTaskMessage)} {options.TableNameOrTreePath} {timeDensity}分钟采集任务推送完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); } + + ///// + ///// 创建Kafka消息 + ///// + ///// + ///// + ///// + //private async Task CreateMeterKafkaTaskMessage( + //string redisCacheTelemetryPacketInfoHashKey, + //string redisCacheTelemetryPacketInfoZSetScoresIndexKey) + //{ + // if (string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoHashKey) || string.IsNullOrWhiteSpace(redisCacheTelemetryPacketInfoHashKey)) + // { + // throw new Exception($"{nameof(CreateMeterKafkaTaskMessage)} 创建Kafka消息失败,参数异常,-101"); + // } + + // decimal? cursor = null; + // string member = null; + // bool hasNext; + // var stopwatch = Stopwatch.StartNew(); + // do + // { + // var page = await _redisDataCacheService.GetAllPagedData( + // redisCacheTelemetryPacketInfoHashKey, + // redisCacheTelemetryPacketInfoZSetScoresIndexKey, + // pageSize: 1000, + // lastScore: cursor, + // lastMember: member); + + // cursor = page.HasNext ? page.NextScore : null; + // member = page.HasNext ? page.NextMember : null; + // hasNext = page.HasNext; + + // await DeviceGroupBalanceControl.ProcessWithThrottleAsync( + // items: page.Items, + // deviceIdSelector: data => data.FocusAddress, + // processor: (data, groupIndex) => + // { + // _ = KafkaProducerIssuedMessageAction(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, data, groupIndex); + // } + // ); + + // } while (hasNext); + + // stopwatch.Stop(); + // _logger.LogError($"{nameof(CreateMeterKafkaTaskMessage)} {redisCacheTelemetryPacketInfoHashKey}采集推送完成,共消耗{stopwatch.ElapsedMilliseconds}毫秒。"); + //} + /// /// Kafka 推送消息 /// @@ -1069,15 +1109,15 @@ namespace JiShe.CollectBus.ScheduledMeterReading /// 任务记录 /// 对应分区,也就是集中器号所在的分组序号 /// - private async Task KafkaProducerIssuedMessageAction(string topicName, - MeterReadingTelemetryPacketInfo taskRecord, int partition) + private async Task KafkaProducerIssuedMessageAction(string topicName, + T taskRecord, int partition) where T : class { if (string.IsNullOrWhiteSpace(topicName) || taskRecord == null) { throw new Exception($"{nameof(KafkaProducerIssuedMessageAction)} 推送消息失败,参数异常,-101"); } - await _producerService.ProduceAsync(topicName, partition, taskRecord); + await _producerService.ProduceAsync(topicName, taskRecord, partition); } #endregion diff --git a/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs b/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs index b8fd08b..fe0746f 100644 --- a/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs +++ b/services/JiShe.CollectBus.Application/ScheduledMeterReading/EnergySystemScheduledMeterReadingService.cs @@ -8,6 +8,7 @@ using JiShe.CollectBus.Common.DeviceBalanceControl; using JiShe.CollectBus.Common.Helpers; using JiShe.CollectBus.FreeSql; using JiShe.CollectBus.GatherItem; +using JiShe.CollectBus.IoTDB.Context; using JiShe.CollectBus.IoTDB.Interface; using JiShe.CollectBus.IotSystems.Devices; using JiShe.CollectBus.IotSystems.MessageIssueds; @@ -35,18 +36,19 @@ namespace JiShe.CollectBus.ScheduledMeterReading public class EnergySystemScheduledMeterReadingService : BasicScheduledMeterReadingService { string serverTagName = string.Empty; + public EnergySystemScheduledMeterReadingService( ILogger logger, IIoTDbProvider dbProvider, - IMeterReadingRecordRepository meterReadingRecordRepository, - IOptions kafkaOptions, + IOptions kafkaOptions, + IoTDBRuntimeContext runtimeContext, IProducerService producerService, IRedisDataCacheService redisDataCacheService) : base(logger, - meterReadingRecordRepository, producerService, redisDataCacheService, dbProvider, + runtimeContext, kafkaOptions) { serverTagName = kafkaOptions.Value.ServerTagName; diff --git a/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs b/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs index 2ef854d..b6dd642 100644 --- a/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs +++ b/services/JiShe.CollectBus.Application/Subscribers/SubscriberAppService.cs @@ -21,6 +21,9 @@ using Volo.Abp.Domain.Repositories; using System.Collections.Generic; using JiShe.CollectBus.Interceptors; using JiShe.CollectBus.Kafka.Internal; +using JiShe.CollectBus.IoTDB.Provider; +using JiShe.CollectBus.Protocol.Dto; +using System.Collections; namespace JiShe.CollectBus.Subscribers { @@ -63,37 +66,27 @@ namespace JiShe.CollectBus.Subscribers [LogIntercept] [KafkaSubscribe(ProtocolConst.SubscriberLoginIssuedEventName, EnableBatch = true)] public async Task LoginIssuedEvent(List issuedEventMessages) - { + { bool isAck = false; foreach (var issuedEventMessage in issuedEventMessages) { - switch (issuedEventMessage.Type) + var loginEntity = await _messageReceivedLoginEventRepository.FirstOrDefaultAsync(a => a.MessageId == issuedEventMessage.MessageId); + if (loginEntity == null) { - case IssuedEventType.Heartbeat: - break; - case IssuedEventType.Login: - _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 登录回复下发内容:{issuedEventMessage.Serialize()}"); - var loginEntity = await _messageReceivedLoginEventRepository.GetAsync(a => a.MessageId == issuedEventMessage.MessageId); - loginEntity.AckTime = Clock.Now; - loginEntity.IsAck = true; - await _messageReceivedLoginEventRepository.UpdateAsync(loginEntity); - isAck = true; - break; - case IssuedEventType.Data: - break; - default: - throw new ArgumentOutOfRangeException(); + isAck=false; + break; } + _logger.LogInformation($"集中器地址{issuedEventMessage.ClientId} 登录回复下发内容:{issuedEventMessage.Serialize()}"); - //var device = await _deviceRepository.FindAsync(a => a.Number == issuedEventMessage.DeviceNo); - //if (device != null) - //{ - // await _tcpService.SendAsync(device.ClientId, issuedEventMessage.Message); - //} + loginEntity.AckTime = Clock.Now; + loginEntity.IsAck = true; + await _messageReceivedLoginEventRepository.UpdateAsync(loginEntity); + if (_tcpService.ClientExists(issuedEventMessage.ClientId)) + await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); + isAck = true; - await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); } - + // TODO:暂时ACK,等后续处理是否放到私信队列中 return isAck? SubscribeAck.Success(): SubscribeAck.Fail(); } @@ -103,31 +96,26 @@ namespace JiShe.CollectBus.Subscribers bool isAck = false; foreach (var issuedEventMessage in issuedEventMessages) { - switch (issuedEventMessage.Type) + var heartbeatEntity = await _messageReceivedHeartbeatEventRepository.FirstOrDefaultAsync(a => a.MessageId == issuedEventMessage.MessageId); + if (heartbeatEntity == null) { - case IssuedEventType.Heartbeat: - _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 心跳回复下发内容:{issuedEventMessage.Serialize()}"); - var heartbeatEntity = await _messageReceivedHeartbeatEventRepository.GetAsync(a => a.MessageId == issuedEventMessage.MessageId); - heartbeatEntity.AckTime = Clock.Now; - heartbeatEntity.IsAck = true; - await _messageReceivedHeartbeatEventRepository.UpdateAsync(heartbeatEntity); - isAck = true; - break; - case IssuedEventType.Data: - break; - default: - throw new ArgumentOutOfRangeException(); + isAck = false; + break; } + _logger.LogWarning($"集中器地址{issuedEventMessage.ClientId} 心跳回复下发内容:{issuedEventMessage.Serialize()}"); + heartbeatEntity.AckTime = Clock.Now; + heartbeatEntity.IsAck = true; + await _messageReceivedHeartbeatEventRepository.UpdateAsync(heartbeatEntity); //var device = await _deviceRepository.FindAsync(a => a.Number == issuedEventMessage.DeviceNo); //if (device != null) //{ // await _tcpService.SendAsync(device.ClientId, issuedEventMessage.Message); //} - - await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); + if(_tcpService.ClientExists(issuedEventMessage.ClientId)) + await _tcpService.SendAsync(issuedEventMessage.ClientId, issuedEventMessage.Message); } - + // TODO:暂时ACK,等后续处理是否放到私信队列中 return isAck ? SubscribeAck.Success() : SubscribeAck.Fail(); } @@ -143,16 +131,14 @@ namespace JiShe.CollectBus.Subscribers } else { - //todo 会根据不同的协议进行解析,然后做业务处理 - TB3761 fN = await protocolPlugin.AnalyzeAsync(receivedMessage); - if(fN == null) + TB3761? tB3761 = protocolPlugin.Analysis3761(receivedMessage.MessageHexString); + if (tB3761 == null) { Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); return SubscribeAck.Success(); } - var tb3761FN = fN.FnList.FirstOrDefault(); - if (tb3761FN == null) + if (tB3761.DT == null || tB3761.AFN_FC == null) { Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); return SubscribeAck.Success(); @@ -162,8 +148,8 @@ namespace JiShe.CollectBus.Subscribers var entity = new MeterReadingRecords() { ReceivedMessageHexString = receivedMessage.MessageHexString, - AFN = fN.Afn, - Fn = tb3761FN.Fn, + AFN = (AFN)tB3761.AFN_FC.AFN, + Fn = tB3761.DT.Fn, Pn = 0, FocusAddress = "", MeterAddress = "", @@ -190,40 +176,79 @@ namespace JiShe.CollectBus.Subscribers [KafkaSubscribe(ProtocolConst.SubscriberHeartbeatReceivedEventName, EnableBatch = true)] public async Task ReceivedHeartbeatEvent(List receivedHeartbeatMessages) { - foreach (var receivedHeartbeatMessage in receivedHeartbeatMessages) - { - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("协议不存在!"); - } - else - { - await protocolPlugin.HeartbeatAsync(receivedHeartbeatMessage); - await _messageReceivedHeartbeatEventRepository.InsertAsync(receivedHeartbeatMessage); - } - } + //foreach (var receivedHeartbeatMessage in receivedHeartbeatMessages) + //{ + // var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + // if (protocolPlugin == null) + // { + // _logger.LogError("协议不存在!"); + // } + // else + // { + // //await protocolPlugin.HeartbeatAsync(receivedHeartbeatMessage); + // await _messageReceivedHeartbeatEventRepository.InsertAsync(receivedHeartbeatMessage); + // } + //} + await _messageReceivedHeartbeatEventRepository.InsertManyAsync(receivedHeartbeatMessages); + return SubscribeAck.Success(); } [KafkaSubscribe(ProtocolConst.SubscriberLoginReceivedEventName,EnableBatch =true)] public async Task ReceivedLoginEvent(List receivedLoginMessages) { - foreach (var receivedLoginMessage in receivedLoginMessages) - { - var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); - if (protocolPlugin == null) - { - _logger.LogError("协议不存在!"); - } - else - { - await protocolPlugin.LoginAsync(receivedLoginMessage); - await _messageReceivedLoginEventRepository.InsertAsync(receivedLoginMessage); - } - } - + //foreach (var receivedLoginMessage in receivedLoginMessages) + //{ + //var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + //if (protocolPlugin == null) + //{ + // _logger.LogError("协议不存在!"); + //} + //else + //{ + // //await protocolPlugin.LoginAsync(receivedLoginMessage); + // await _messageReceivedLoginEventRepository.InsertAsync(receivedLoginMessage); + //} + + //} + await _messageReceivedLoginEventRepository.InsertManyAsync(receivedLoginMessages); return SubscribeAck.Success(); } + + + [KafkaSubscribe(ProtocolConst.SubscriberAFN02HReceivedEventNameTemp)] + public async Task ReceivedAFN00Event(MessageReceived receivedMessage) + { + + var protocolPlugin = _serviceProvider.GetKeyedService("StandardProtocolPlugin"); + if (protocolPlugin == null) + { + _logger.LogError("协议不存在!"); + } + else + { + TB3761? tB3761 = protocolPlugin.Analysis3761(receivedMessage.MessageHexString); + if (tB3761 == null) + { + Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); + return SubscribeAck.Success(); + } + if (tB3761.DT == null || tB3761.AFN_FC == null) + { + Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}"); + return SubscribeAck.Success(); + } + string serverName = $"AFN{tB3761.AFN_FC.AFN}_F{tB3761.DT.Fn}_Analysis"; + //var analysisStrategy = _serviceProvider.GetKeyedService($"AFN0_F1_Analysis"); + + //var data = await analysisStrategy.ExecuteAsync>(tB3761); + var executor = _serviceProvider.GetRequiredService(); + AFN0_F1_AnalysisDto aFN0_F1_AnalysisDto= await executor.ExecuteAsync("AFN0_F1_Analysis", tB3761); + } + + return SubscribeAck.Success(); + } + + } } diff --git a/services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs b/services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs index c07950f..add3131 100644 --- a/services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs +++ b/services/JiShe.CollectBus.Domain/Ammeters/AmmeterInfo.cs @@ -14,7 +14,7 @@ namespace JiShe.CollectBus.Ammeters /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 /// [Column(IsIgnore = true)] - public override string MemberId => $"{FocusId}:{MeterId}"; + public override string MemberId => $"{FocusAddress}:{MeteringCode}"; /// /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 diff --git a/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs index 2d9cc67..ee132d3 100644 --- a/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs +++ b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeter.cs @@ -4,10 +4,12 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using JiShe.CollectBus.IoTDB.Attribute; -using JiShe.CollectBus.IoTDB.Provider; +using JiShe.CollectBus.IoTDB.Enums; +using JiShe.CollectBus.IoTDB.Model; namespace JiShe.CollectBus.Ammeters { + [EntityType(EntityTypeEnum.TableModel)] public class ElectricityMeter : IoTEntity { [ATTRIBUTEColumn] @@ -33,5 +35,8 @@ namespace JiShe.CollectBus.Ammeters [FIELDColumn] public double Power => Voltage * Current; + + [FIELDColumn] + public double? Currentd { get; set; } } } diff --git a/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeterTreeModel.cs b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeterTreeModel.cs new file mode 100644 index 0000000..7fe7ebc --- /dev/null +++ b/services/JiShe.CollectBus.Domain/Ammeters/ElectricityMeterTreeModel.cs @@ -0,0 +1,37 @@ +using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Enums; +using JiShe.CollectBus.IoTDB.Model; + +namespace JiShe.CollectBus.Ammeters +{ + [EntityType(EntityTypeEnum.TreeModel)] + public class ElectricityMeterTreeModel : IoTEntity + { + [ATTRIBUTEColumn] + public string MeterModel { get; set; } + + /// + /// 下发消息内容 + /// + [FIELDColumn] + public string IssuedMessageHexString { get; set; } + + ///// + ///// 下发消息Id + ///// + //[FIELDColumn] + //public string IssuedMessageId { get; set; } + + [FIELDColumn] + public double Voltage { get; set; } + + [FIELDColumn] + public double Current { get; set; } + + [FIELDColumn] + public double Power => Voltage * Current; + + [FIELDColumn] + public double? Currentd { get; set; } + } +} diff --git a/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs b/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs index 3aafa41..3ac4202 100644 --- a/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs +++ b/services/JiShe.CollectBus.Domain/IotSystems/MeterReadingRecords/MeterReadingTelemetryPacketInfo.cs @@ -1,5 +1,9 @@ -using JiShe.CollectBus.Common.Enums; +using JiShe.CollectBus.Common.Encrypt; +using JiShe.CollectBus.Common.Enums; using JiShe.CollectBus.Common.Models; +using JiShe.CollectBus.IoTDB.Attribute; +using JiShe.CollectBus.IoTDB.Enums; +using JiShe.CollectBus.IoTDB.Model; using System; using System.Collections.Generic; using System.Linq; @@ -13,78 +17,79 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// /// 抄读任务Redis缓存数据记录 /// - public class MeterReadingTelemetryPacketInfo : DeviceCacheBasicModel + [EntityType(EntityTypeEnum.TableModel)] + public class MeterReadingTelemetryPacketInfo : IoTEntity { /// - /// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义 + /// 排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳、或者某一个固定的标识1 /// - public override string MemberId => $"{FocusId}:{MeterId}:{ItemCode}"; - - /// - /// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳 - /// - public override long ScoreValue => ((long)FocusId << 32) | (uint)DateTime.Now.Ticks; - - + [FIELDColumn] + public string ScoreValue + { + get + { + return $"{DeviceId}.{TaskMark}".Md5Fun(); + } + } + /// /// 是否手动操作 /// + [FIELDColumn] public bool ManualOrNot { get; set; } /// /// 任务数据唯一标记 /// - public decimal TaskMark { get; set; } - - /// - /// 时间戳标记,IoTDB时间列处理,上报通过构建标记获取唯一标记匹配时间戳。 - /// - public long Timestamps { get; set; } + [FIELDColumn] + public string TaskMark { get; set; } /// /// 是否超时 /// + [FIELDColumn] public bool IsTimeout { get; set; } = false; /// /// 待抄读时间 /// + [FIELDColumn] public DateTime PendingCopyReadTime { get; set; } - - + /// - /// 集中器地址 + /// 集中器Id /// - public string FocusAddress { get; set; } - + [FIELDColumn] + public int FocusId { get; set; } + + /// + /// 表Id + /// + [FIELDColumn] + public int MeterId { get; set; } + /// /// 表地址 /// + [FIELDColumn] public string MeterAddress { get; set; } - /// - /// 表类型 - /// - public MeterTypeEnum MeterType { get; set; } - - /// - /// 项目ID - /// - public int ProjectID { get; set; } - /// /// 数据库业务ID /// + [FIELDColumn] public int DatabaseBusiID { get; set; } /// /// AFN功能码 /// - public AFN AFN { get; set; } + [FIELDColumn] + public int AFN { get; set; } /// /// 抄读功能码 /// + [FIELDColumn] public int Fn { get; set; } /// @@ -95,66 +100,73 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords /// /// 采集项编码 /// - public string ItemCode { get; set;} + [FIELDColumn] + public string ItemCode { get; set; } - /// - /// 帧序列域SEQ - /// - public required Seq Seq { get; set; } + ///// + ///// 帧序列域SEQ + ///// + //public required Seq Seq { get; set; } /// /// 地址域A3的主站地址MSA /// + [FIELDColumn] public int MSA { get; set; } /// /// 是否发送 /// + [FIELDColumn] public bool IsSend { get; set; } /// /// 创建时间 /// + [FIELDColumn] public DateTime CreationTime { get; set; } /// /// 下发消息内容 /// + [FIELDColumn] public string IssuedMessageHexString { get; set; } /// /// 下发消息Id /// + [FIELDColumn] public string IssuedMessageId { get; set; } /// /// 消息上报内容 /// + [FIELDColumn] public string? ReceivedMessageHexString { get; set; } /// /// 消息上报时间 /// + [FIELDColumn] public DateTime? ReceivedTime { get; set; } /// /// 上报消息Id /// - public string ReceivedMessageId { get; set; } + [FIELDColumn] + public string ReceivedMessageId { get; set; } /// /// 上报报文解析备注,异常情况下才有 /// + [FIELDColumn] public string ReceivedRemark { get; set; } /// /// 是否已上报 /// - public bool IsReceived { get; set; } - - //public void CreateDataId(Guid Id) - //{ - // this.Id = Id; - //} + [FIELDColumn] + public bool IsReceived { get; set; } + } } diff --git a/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs index f4dd11d..a96c16a 100644 --- a/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs +++ b/shared/JiShe.CollectBus.Common/BuildSendDatas/Build3761SendData.cs @@ -375,22 +375,30 @@ namespace JiShe.CollectBus.Common.BuildSendDatas #region 排序 var timeSets = timeSetDetails; - Dictionary dicTsDetails = new Dictionary(); + // 旧 + //Dictionary dicTsDetails = new Dictionary(); + //foreach (var timeSet in timeSets) + //{ + // int firstMonty = timeSet.Months[0]; + // if (!dicTsDetails.Keys.Contains(firstMonty)) + // dicTsDetails.Add(firstMonty, timeSet); + //} + + //var sortKeys = dicTsDetails.Keys.OrderBy(n => n).ToList(); + //List orderTsDetails = new List(); + //foreach (var key in sortKeys) + //{ + // orderTsDetails.Add(dicTsDetails[key]); + //} + + //timeSetDetails = orderTsDetails; + + // 新 foreach (var timeSet in timeSets) { - int firstMonty = timeSet.Months[0]; - if (!dicTsDetails.Keys.Contains(firstMonty)) - dicTsDetails.Add(firstMonty, timeSet); + timeSet.Months = timeSet.Months.OrderBy(m => m).ToArray(); } - - var sortKeys = dicTsDetails.Keys.OrderBy(n => n).ToList(); - List orderTsDetails = new List(); - foreach (var key in sortKeys) - { - orderTsDetails.Add(dicTsDetails[key]); - } - - timeSetDetails = orderTsDetails; + timeSetDetails = timeSets; #endregion diff --git a/shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs b/shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs index 5184459..6420a23 100644 --- a/shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs +++ b/shared/JiShe.CollectBus.Common/BuildSendDatas/TasksToBeIssueModel.cs @@ -11,6 +11,11 @@ namespace JiShe.CollectBus.Common.BuildSendDatas /// public class TasksToBeIssueModel { + /// + /// 上次下发任务的时间 + /// + public DateTime? LastTaskTime { get; set; } + /// /// 下个任务时间 /// diff --git a/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs b/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs index 6b65535..5f8e441 100644 --- a/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs +++ b/shared/JiShe.CollectBus.Common/Consts/ProtocolConst.cs @@ -98,22 +98,22 @@ namespace JiShe.CollectBus.Common.Consts /// /// AFN00H上行主题格式 /// - public const string SubscriberAFN00ReceivedEventNameTemp = "received.afn00h.event"; + public const string SubscriberAFN00HReceivedEventNameTemp = "received.afn00h.event"; /// /// AFN01H上行主题格式 /// - public const string SubscriberAFN00HReceivedEventNameTemp = "received.afn01h.event"; + public const string SubscriberAFN01HReceivedEventNameTemp = "received.afn01h.event"; /// /// AFN02H上行主题格式 /// - public const string SubscriberAFN01HReceivedEventNameTemp = "received.afn02h.event"; + public const string SubscriberAFN02HReceivedEventNameTemp = "received.afn02h.event"; /// /// AFN03H上行主题格式 /// - public const string SubscriberAFN02HReceivedEventNameTemp = "received.afn03h.event"; + public const string SubscriberAFN03HReceivedEventNameTemp = "received.afn03h.event"; /// /// AFN04H上行主题格式 diff --git a/shared/JiShe.CollectBus.Common/Encrypt/EncryptUtil.cs b/shared/JiShe.CollectBus.Common/Encrypt/EncryptUtil.cs new file mode 100644 index 0000000..72cdf41 --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Encrypt/EncryptUtil.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Encrypt +{ + /// + /// 各种加密辅助类 + /// + public static class EncryptUtil + { + #region MD5加密 + + /// + /// MD5加密 + /// + public static string Md5Fun(this string value) + { + if (value == null) + { + throw new ArgumentNullException("未将对象引用设置到对象的实例。"); + } + + var encoding = Encoding.UTF8; + MD5 md5 = MD5.Create(); + return HashAlgorithmBase(md5, value, encoding); + } + + /// + /// 加权MD5加密 + /// + public static string Md5Fun(this string value, string salt) + { + return salt == null ? value.Md5Fun() : (value + "『" + salt + "』").Md5Fun(); + } + + #endregion + + /// + /// HashAlgorithm 加密统一方法 + /// + private static string HashAlgorithmBase(HashAlgorithm hashAlgorithmObj, string source, Encoding encoding) + { + byte[] btStr = encoding.GetBytes(source); + byte[] hashStr = hashAlgorithmObj.ComputeHash(btStr); + return hashStr.Bytes2Str(); + } + + /// + /// 转换成字符串 + /// + private static string Bytes2Str(this IEnumerable source, string formatStr = "{0:X2}") + { + StringBuilder pwd = new StringBuilder(); + foreach (byte btStr in source) + { + pwd.AppendFormat(formatStr, btStr); + } + return pwd.ToString(); + } + } +} diff --git a/shared/JiShe.CollectBus.Common/Enums/376Enums.cs b/shared/JiShe.CollectBus.Common/Enums/376Enums.cs index 68df1da..6ea182d 100644 --- a/shared/JiShe.CollectBus.Common/Enums/376Enums.cs +++ b/shared/JiShe.CollectBus.Common/Enums/376Enums.cs @@ -368,4 +368,9 @@ namespace JiShe.CollectBus.Common.Enums HardwareReleaseDate=38 } + public enum FN + { + 登录 = 1, + 心跳 = 3 + } } diff --git a/shared/JiShe.CollectBus.Common/Enums/TimestampUnit.cs b/shared/JiShe.CollectBus.Common/Enums/TimestampUnit.cs new file mode 100644 index 0000000..9bed83f --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Enums/TimestampUnit.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Enums +{ + public enum TimestampUnit + { + Seconds, // 秒级(Unix 时间戳) + Milliseconds, // 毫秒级(默认) + Microseconds, // 微秒级 + Nanoseconds // 纳秒级 + } +} diff --git a/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs index e6136df..6936ad3 100644 --- a/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs +++ b/shared/JiShe.CollectBus.Common/Extensions/DateTimeExtensions.cs @@ -181,25 +181,7 @@ namespace JiShe.CollectBus.Common.Extensions return $"{dateTime:yyyyMMddHH}"; #endif } - - /// - /// 获取当前时间毫秒级时间戳 - /// - /// - public static long GetCurrentTimeMillis() - { - return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - } - - /// - /// 将Unix时间戳转换为日期时间 - /// - /// - /// - public static DateTime FromUnixMillis(long millis) - { - return DateTimeOffset.FromUnixTimeMilliseconds(millis).DateTime; - } + /// /// 采集时间节点计算 @@ -233,5 +215,43 @@ namespace JiShe.CollectBus.Common.Extensions .AddHours(hours) .AddMinutes(minutes); } + + + /// + /// 格式化为微秒(μs) + /// + /// + /// + public static string ToMicrosecondString(this DateTime dt) + { + long microseconds = (dt.Ticks % TimeSpan.TicksPerSecond) / 10; // 1 Tick = 100ns → 0.1μs + return $"{dt:yyyy-MM-dd HH:mm:ss.fffffff}".Remove(23) + $"{microseconds:D6}"; + } + + /// + /// 格式化为纳秒(ns) + /// + /// + /// + public static string ToNanosecondString(this DateTime dt) + { + long nanoseconds = (dt.Ticks % TimeSpan.TicksPerSecond) * 100; // 1 Tick = 100ns + return $"{dt:yyyy-MM-dd HH:mm:ss.fffffff}".Remove(23) + $"{nanoseconds:D9}"; + } + + /// + /// 毫米、微秒、纳秒时间戳转DateTime + /// + /// + /// + /// + public static DateTime ParseIntToDate(long dateLong) + { + if (dateLong < 10000101 || dateLong > 99991231) + { + throw new ArgumentException("Date must be between 10000101 and 99991231."); + } + return DateTime.TryParseExact(dateLong.ToString(), "yyyyMMdd HHmmssZZ", null, System.Globalization.DateTimeStyles.None, out DateTime date) ? date : throw new ArgumentException("Date must be between 10000101 and 99991231."); + } } } diff --git a/shared/JiShe.CollectBus.Common/Extensions/DateTimeOffsetExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/DateTimeOffsetExtensions.cs new file mode 100644 index 0000000..0f07e9c --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Extensions/DateTimeOffsetExtensions.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace JiShe.CollectBus.Common.Extensions +{ + public static class DateTimeOffsetExtensions + { + + /// + /// 获取当前时间毫秒级时间戳 + /// + /// + public static long GetCurrentTimeMillis() + { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + } + + /// + /// 将Unix时间戳转换为日期时间 + /// + /// + /// + public static DateTime FromUnixMillis(long millis) + { + return DateTimeOffset.FromUnixTimeMilliseconds(millis).DateTime; + } + + /// + /// 将 DateTime 时间转换为 DateTimeOffset 时间 + /// + /// + /// + public static DateTimeOffset GetDateTimeOffset(this DateTime rawDateTime) + { + //确保 Kind 为 Local(如果是 Unspecified) + DateTime localDateTime = rawDateTime.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(rawDateTime, DateTimeKind.Local) + : rawDateTime; + + // 转换为 DateTimeOffset(自动应用本地时区偏移) + return new DateTimeOffset(localDateTime); + } + } +} diff --git a/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs b/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs index 0c77e37..9ea2120 100644 --- a/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs +++ b/shared/JiShe.CollectBus.Common/Extensions/StringExtensions.cs @@ -1128,6 +1128,21 @@ namespace JiShe.CollectBus.Common.Extensions return string.Join(" ", strArr.Reverse()); } + + /// + /// 高低位反转,并转换成字符串 + /// + /// 16进制字符集合 + /// 16进制字符集合开始索引 + /// 取多少个数据 + /// + public static string ListReverseToStr(this List list, int index, int count) + { + var addrList = list.GetRange(index, count); + addrList.Reverse();//高低位反转 + return string.Join("", addrList); + } + /// /// 二进制转十六进制 /// @@ -1174,6 +1189,25 @@ namespace JiShe.CollectBus.Common.Extensions return binaryValue; } + + /// + /// 十六进制转二进制 + /// 不足4位,前面补0 + /// + /// + /// + public static string HexTo4BinZero(this string hexString) + { + string result = string.Empty; + foreach (char c in hexString) + { + int v = Convert.ToInt32(c.ToString(), 16); + int v2 = int.Parse(Convert.ToString(v, 2)); + result += string.Format("{0:d4}", v2); + } + return result; + } + /// /// 数据值加33 /// diff --git a/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs index 3c36d23..34cf37f 100644 --- a/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs +++ b/shared/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -769,11 +769,11 @@ namespace JiShe.CollectBus.Common.Helpers /// /// /// - public static decimal GetTaskMark(int afn, int fn, int pn, int msa) + public static string GetTaskMark(int afn, int fn, int pn, int msa) { var makstr = $"{afn.ToString().PadLeft(2, '0')}{fn.ToString().PadLeft(2, '0')}{pn.ToString().PadLeft(2, '0')}"; - return Convert.ToInt32(makstr) << 32 | msa; + return makstr;// Convert.ToInt32(makstr) << 32 | msa; } } } diff --git a/shared/JiShe.CollectBus.Common/Helpers/TimestampHelper.cs b/shared/JiShe.CollectBus.Common/Helpers/TimestampHelper.cs new file mode 100644 index 0000000..9edc3cd --- /dev/null +++ b/shared/JiShe.CollectBus.Common/Helpers/TimestampHelper.cs @@ -0,0 +1,68 @@ +using JiShe.CollectBus.Common.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Helpers +{ + /// + /// 时间戳帮助类 + /// + public static class TimestampHelper + { + private static readonly long UnixEpochTicks = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).Ticks; + + /// + /// 获取当前 DateTimeOffset 距离 Unix 纪元(1970-01-01)的微秒数 + /// + public static long ToUnixTimeMicroseconds(this DateTimeOffset dateTimeOffset) + { + // Ticks 单位是 100 纳秒,转换为微秒需除以 10 + long elapsedTicks = dateTimeOffset.Ticks - UnixEpochTicks; + return elapsedTicks / 10; // 1 微秒 = 1000 纳秒 = 10 Ticks + } + + /// + /// 获取当前 DateTimeOffset 距离 Unix 纪元(1970-01-01)的纳秒数 + /// + public static long ToUnixTimeNanoseconds(this DateTimeOffset dateTimeOffset) + { + long nanoseconds = (dateTimeOffset.Ticks - UnixEpochTicks) * 100; + return nanoseconds; + } + + /// + /// 将 long 类型时间戳转换为 DateTime(UTC) + /// + /// 时间戳数值 + /// 时间戳单位 + public static DateTime ConvertToDateTime(long timestamp, TimestampUnit unit = TimestampUnit.Milliseconds) + { + long ticks = unit switch + { + TimestampUnit.Seconds => checked(timestamp * TimeSpan.TicksPerSecond), + TimestampUnit.Milliseconds => checked(timestamp * TimeSpan.TicksPerMillisecond), + TimestampUnit.Microseconds => checked(timestamp * 10), // 1微秒 = 10 Ticks(100纳秒) + TimestampUnit.Nanoseconds => checked(timestamp / 100),// 1 Tick = 100纳秒 + _ => throw new ArgumentException("无效的时间单位", nameof(unit)) + }; + + try + { + DateTime result = new DateTime(UnixEpochTicks + ticks, DateTimeKind.Utc); + // 校验结果是否在 DateTime 合法范围内(0001-01-01 至 9999-12-31) + if (result < DateTime.MinValue || result > DateTime.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(timestamp), "时间戳超出 DateTime 范围"); + } + return result; + } + catch (ArgumentOutOfRangeException ex) + { + throw new ArgumentOutOfRangeException("时间戳无效", ex); + } + } + } +} diff --git a/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml b/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml index afe25da..0193df3 100644 --- a/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml +++ b/web/JiShe.CollectBus.Host/Pages/Monitor.cshtml @@ -16,8 +16,9 @@ 后端服务 + - +