合并
This commit is contained in:
commit
3db0db19ea
@ -0,0 +1,19 @@
|
||||
using JiShe.CollectBus.IoTDB.Enums;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB实体类型特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class EntityTypeAttribute : System.Attribute
|
||||
{
|
||||
public EntityTypeEnum EntityType { get; }
|
||||
|
||||
|
||||
public EntityTypeAttribute(EntityTypeEnum entityType)
|
||||
{
|
||||
EntityType = entityType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple<string,object>,Item1=>测点名称,Item2=>测点值,泛型
|
||||
/// 用于标识当前实体为单侧点模式,单侧点模式只有一个Filed标识字段,类型是Tuple<string,T>,Item1=>测点名称,Item2=>测点值,泛型
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class SingleMeasuringAttribute : System.Attribute
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
using JiShe.CollectBus.IoTDB.Enums;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB实体存储路径或表名称,一般用于已经明确的存储路径或表名称,例如日志存储
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class TableNameOrTreePathAttribute : System.Attribute
|
||||
{
|
||||
public string TableNameOrTreePath { get; }
|
||||
|
||||
public TableNameOrTreePathAttribute(string tableNameOrTreePath)
|
||||
{
|
||||
TableNameOrTreePath = tableNameOrTreePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ public class CollectBusIoTDbModule : AbpModule
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
Configure<IoTDbOptions>(options => { configuration.GetSection(nameof(IoTDbOptions)).Bind(options); });
|
||||
|
||||
// 注册上下文为Scoped
|
||||
context.Services.AddScoped<IoTDbRuntimeContext>();
|
||||
//// 注册上下文为Scoped
|
||||
//context.Services.AddScoped<IoTDBRuntimeContext>();
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,24 @@
|
||||
using JiShe.CollectBus.IoTDB.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Context
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB SessionPool 运行时上下文
|
||||
/// </summary>
|
||||
public class IoTDbRuntimeContext
|
||||
public class IoTDBRuntimeContext: IScopedDependency
|
||||
{
|
||||
private readonly bool _defaultValue;
|
||||
|
||||
public IoTDbRuntimeContext(IOptions<IoTDbOptions> options)
|
||||
public IoTDBRuntimeContext(IOptions<IoTDbOptions> options)
|
||||
{
|
||||
_defaultValue = options.Value.UseTableSessionPoolByDefault;
|
||||
UseTableSessionPool = _defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用表模型存储, 默认false,使用tree模型存储
|
||||
/// 存储模型切换标识,是否使用table模型存储, 默认为false,标识tree模型存储
|
||||
/// </summary>
|
||||
public bool UseTableSessionPool { get; set; }
|
||||
|
||||
|
||||
24
modules/JiShe.CollectBus.IoTDB/EnumInfo/EntityTypeEnum.cs
Normal file
24
modules/JiShe.CollectBus.IoTDB/EnumInfo/EntityTypeEnum.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB实体类型枚举
|
||||
/// </summary>
|
||||
public enum EntityTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 树模型
|
||||
/// </summary>
|
||||
TreeModel = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 表模型
|
||||
/// </summary>
|
||||
TableModel = 2,
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
/// <returns></returns>
|
||||
Task BatchInsertAsync<T>(IEnumerable<T> entities) where T : IoTEntity;
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="deviceMetadata">设备元数据</param>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
Task BatchInsertAsync<T>(DeviceMetadata deviceMetadata,IEnumerable<T> entities) where T : IoTEntity;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 删除数据
|
||||
@ -38,7 +48,14 @@ namespace JiShe.CollectBus.IoTDB.Interface
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
Task<object> DeleteAsync<T>(QueryOptions options) where T : IoTEntity;
|
||||
Task<object> DeleteAsync<T>(IoTDBQueryOptions options) where T : IoTEntity;
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备元数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
Task<DeviceMetadata> GetMetadata<T>() where T : IoTEntity;
|
||||
|
||||
/// <summary>
|
||||
/// 查询数据
|
||||
@ -46,6 +63,6 @@ namespace JiShe.CollectBus.IoTDB.Interface
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
Task<BusPagedResult<T>> QueryAsync<T>(QueryOptions options) where T : IoTEntity, new();
|
||||
Task<BusPagedResult<T>> QueryAsync<T>(IoTDBQueryOptions options) where T : IoTEntity, new();
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!--<PackageReference Include="Apache.IoTDB" Version="1.3.3.1" />-->
|
||||
<PackageReference Include="Apache.IoTDB" Version="2.0.2" />
|
||||
<PackageReference Include="Volo.Abp" Version="8.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
39
modules/JiShe.CollectBus.IoTDB/Model/IoTEntity.cs
Normal file
39
modules/JiShe.CollectBus.IoTDB/Model/IoTEntity.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using JiShe.CollectBus.IoTDB.Attribute;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// IoT实体基类,此类适用于多个数据测点记录场景,单个测点请使用子类 SingleMeasuring
|
||||
/// </summary>
|
||||
public abstract class IoTEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统名称
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string SystemName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 项目编码
|
||||
/// </summary>
|
||||
[ATTRIBUTEColumn]
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型集中器、电表、水表、流量计、传感器等
|
||||
/// </summary>
|
||||
[ATTRIBUTEColumn]
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备ID,也就是通信设备的唯一标识符,例如集中器地址,或者其他传感器设备地址
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时标,也就是业务时间戳,单位毫秒,必须通过DateTimeOffset获取
|
||||
/// </summary>
|
||||
public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Table模型单项数据实体
|
||||
/// </summary>
|
||||
[EntityType(EntityTypeEnum.TableModel)]
|
||||
public class TableModelSingleMeasuringEntity<T> : IoTEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 单项数据键值对
|
||||
/// </summary>
|
||||
[SingleMeasuring(nameof(SingleColumn))]
|
||||
public required Tuple<string, T> SingleColumn { get; set; }
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// AFN单项数据实体
|
||||
/// Tree模型单项数据实体
|
||||
/// </summary>
|
||||
public class SingleMeasuringAFNDataEntity<T> : IoTEntity
|
||||
[EntityType(EntityTypeEnum.TreeModel)]
|
||||
public class TreeModelSingleMeasuringEntity<T> : IoTEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 单项数据对象
|
||||
/// 单项数据键值对
|
||||
/// </summary>
|
||||
[SingleMeasuring(nameof(SingleMeasuring))]
|
||||
public Tuple<string, T> SingleMeasuring { get; set; }
|
||||
public required Tuple<string, T> SingleMeasuring { get; set; }
|
||||
}
|
||||
}
|
||||
@ -42,5 +42,10 @@
|
||||
/// 是否使用表模型存储, 默认false,使用tree模型存储
|
||||
/// </summary>
|
||||
public bool UseTableSessionPoolByDefault { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 时区,默认为:"UTC+08:00"
|
||||
/// </summary>
|
||||
public string ZoneId { get; set; } = "UTC+08:00";
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// 查询条件
|
||||
/// </summary>
|
||||
public class QueryOptions
|
||||
public class IoTDBQueryOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 表模型的表名称或者树模型的设备路径
|
||||
@ -13,7 +13,7 @@
|
||||
/// <summary>
|
||||
/// 分页
|
||||
/// </summary>
|
||||
public int Page { get; set; }
|
||||
public int PageIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分页大小
|
||||
@ -23,6 +23,6 @@
|
||||
/// <summary>
|
||||
/// 查询条件
|
||||
/// </summary>
|
||||
public List<QueryCondition> Conditions { get; } = new();
|
||||
public List<QueryCondition> Conditions { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@ -9,10 +9,17 @@
|
||||
/// 字段
|
||||
/// </summary>
|
||||
public string Field { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作符
|
||||
/// 操作符,>,=,<
|
||||
/// </summary>
|
||||
public string Operator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否数值,如果是数值,则进行数值比较,否则进行字符串比较
|
||||
/// </summary>
|
||||
public bool IsNumber { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
|
||||
@ -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
|
||||
/// </summary>
|
||||
public class DeviceMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTDB实体类型枚举
|
||||
/// </summary>
|
||||
public EntityTypeEnum EntityType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否有单测量值
|
||||
/// </summary>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace JiShe.CollectBus.IoTDB.Provider
|
||||
using JiShe.CollectBus.IoTDB.Model;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备路径构建器
|
||||
@ -13,7 +15,7 @@
|
||||
/// <returns></returns>
|
||||
public static string GetDevicePath<T>(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()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取表名称,用作单侧点表模型特殊处理。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDeviceTableName<T>(T entity) where T : IoTEntity
|
||||
{
|
||||
return $"{entity.SystemName.ToLower()}.`{entity.DeviceId}`";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<Type, DeviceMetadata> MetadataCache = new();
|
||||
private readonly ILogger<IoTDbProvider> _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<IoTDbProvider> logger,
|
||||
IIoTDbSessionFactory sessionFactory,
|
||||
IoTDbRuntimeContext runtimeContext)
|
||||
IoTDBRuntimeContext runtimeContext)
|
||||
{
|
||||
_logger = logger;
|
||||
_sessionFactory = sessionFactory;
|
||||
@ -51,17 +58,19 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
||||
/// <returns></returns>
|
||||
public async Task InsertAsync<T>(T entity) where T : IoTEntity
|
||||
{
|
||||
var metadata = GetMetadata<T>();
|
||||
try
|
||||
{
|
||||
var metadata = await GetMetadata<T>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -71,20 +80,51 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
||||
/// <returns></returns>
|
||||
public async Task BatchInsertAsync<T>(IEnumerable<T> entities) where T : IoTEntity
|
||||
{
|
||||
var metadata = GetMetadata<T>();
|
||||
|
||||
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<T>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="deviceMetadata">设备元数据</param>
|
||||
/// <param name="entities"></param>
|
||||
/// <returns></returns>
|
||||
public async Task BatchInsertAsync<T>(DeviceMetadata deviceMetadata, IEnumerable<T> 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
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<object> DeleteAsync<T>(QueryOptions options) where T : IoTEntity
|
||||
public async Task<object> DeleteAsync<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||||
{
|
||||
var query = BuildDeleteSQL<T>(options);
|
||||
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||||
|
||||
if (!sessionDataSet.HasNext())
|
||||
try
|
||||
{
|
||||
_logger.LogWarning($"{typeof(T).Name} 删除数据时,没有返回受影响记录数量。");
|
||||
return 0;
|
||||
}
|
||||
var query = await BuildDeleteSQL<T>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备元数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public async Task<DeviceMetadata> GetMetadata<T>() where T : IoTEntity
|
||||
{
|
||||
var columns = CollectColumnMetadata(typeof(T));
|
||||
var metadata = BuildDeviceMetadata<T>(columns);
|
||||
var metaData = MetadataCache.AddOrUpdate(
|
||||
typeof(T),
|
||||
addValueFactory: t => metadata, // 如果键不存在,用此值添加
|
||||
updateValueFactory: (t, existingValue) =>
|
||||
{
|
||||
var columns = CollectColumnMetadata(t);
|
||||
var metadata = BuildDeviceMetadata<T>(columns);
|
||||
|
||||
//对现有值 existingValue 进行修改,返回新值
|
||||
existingValue.ColumnNames = metadata.ColumnNames;
|
||||
return existingValue;
|
||||
}
|
||||
);
|
||||
|
||||
return await Task.FromResult(metaData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -117,18 +191,32 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<BusPagedResult<T>> QueryAsync<T>(QueryOptions options) where T : IoTEntity, new()
|
||||
public async Task<BusPagedResult<T>> QueryAsync<T>(IoTDBQueryOptions options) where T : IoTEntity, new()
|
||||
{
|
||||
var query = BuildQuerySQL<T>(options);
|
||||
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||||
|
||||
var result = new BusPagedResult<T>
|
||||
try
|
||||
{
|
||||
TotalCount = await GetTotalCount<T>(options),
|
||||
Items = ParseResults<T>(sessionDataSet, options.PageSize)
|
||||
};
|
||||
var query =await BuildQuerySQL<T>(options);
|
||||
var sessionDataSet = await CurrentSession.ExecuteQueryStatementAsync(query);
|
||||
|
||||
return result;
|
||||
|
||||
var result = new BusPagedResult<T>
|
||||
{
|
||||
TotalCount = await GetTotalCount<T>(options),
|
||||
Items = await ParseResults<T>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -146,10 +234,39 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
||||
List<string> tempColumnNames = new List<string>();
|
||||
tempColumnNames.AddRange(metadata.ColumnNames);
|
||||
|
||||
var entityTypeAttribute = typeof(T).GetCustomAttribute<EntityTypeAttribute>();
|
||||
|
||||
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<TableNameOrTreePathAttribute>();
|
||||
if (tableNameOrTreePathAttribute != null)
|
||||
{
|
||||
tableNameOrTreePath = tableNameOrTreePathAttribute.TableNameOrTreePath;
|
||||
}
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
timestamps.Add(entity.Timestamps);
|
||||
var rowValues = new List<object>();
|
||||
|
||||
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<T>());
|
||||
if (!_runtimeContext.UseTableSessionPool)//树模型
|
||||
{
|
||||
devicePaths.Add(DevicePathBuilder.GetDevicePath(entity));
|
||||
}
|
||||
else
|
||||
{
|
||||
devicePaths.Add(DevicePathBuilder.GetTableName<T>());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (devicePaths.Count > 1)
|
||||
@ -220,14 +365,15 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
||||
/// <summary>
|
||||
/// 构建tree模型的Tablet
|
||||
/// </summary>
|
||||
/// <param name="metadata"></param>
|
||||
/// <param name="devicePath"></param>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="timestamps"></param>
|
||||
/// <param name="metadata">已解析的设备数据元数据</param>
|
||||
/// <param name="devicePath">设备路径</param>
|
||||
/// <param name="values">数据集合</param>
|
||||
/// <param name="timestamps">时间戳集合</param>
|
||||
/// <returns></returns>
|
||||
private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath,
|
||||
List<List<object>> values, List<long> timestamps)
|
||||
private Tablet BuildSessionTablet(DeviceMetadata metadata, string devicePath, List<List<object>> values, List<long> timestamps)
|
||||
{
|
||||
//todo 树模型需要去掉TAG类型和ATTRIBUTE类型的字段,只需要保留FIELD类型字段即可
|
||||
|
||||
return new Tablet(
|
||||
devicePath,
|
||||
metadata.ColumnNames,
|
||||
@ -240,16 +386,15 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
||||
/// <summary>
|
||||
/// 构建表模型的Tablet
|
||||
/// </summary>
|
||||
/// <param name="metadata"></param>
|
||||
/// <param name="devicePath"></param>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="timestamps"></param>
|
||||
/// <param name="metadata">已解析的设备数据元数据</param>
|
||||
/// <param name="tableName">表名称</param>
|
||||
/// <param name="values">数据集合</param>
|
||||
/// <param name="timestamps">时间戳集合</param>
|
||||
/// <returns></returns>
|
||||
private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string devicePath,
|
||||
List<List<object>> values, List<long> timestamps)
|
||||
private Tablet BuildTableSessionTablet(DeviceMetadata metadata, string tableName, List<List<object>> values, List<long> timestamps)
|
||||
{
|
||||
var tablet = new Tablet(
|
||||
devicePath,
|
||||
tableName,
|
||||
metadata.ColumnNames,
|
||||
metadata.ColumnCategories,
|
||||
metadata.DataTypes,
|
||||
@ -266,9 +411,9 @@ namespace JiShe.CollectBus.IoTDB.Provider
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
private string BuildQuerySQL<T>(QueryOptions options) where T : IoTEntity
|
||||
private async Task<string> BuildQuerySQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||||
{
|
||||
var metadata = GetMetadata<T>();
|
||||
var metadata = await GetMetadata<T>();
|
||||
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
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
private string BuildDeleteSQL<T>(QueryOptions options) where T : IoTEntity
|
||||
private async Task<string> BuildDeleteSQL<T>(IoTDBQueryOptions options) where T : IoTEntity
|
||||
{
|
||||
var metadata = GetMetadata<T>();
|
||||
var metadata = await GetMetadata<T>();
|
||||
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
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<int> GetTotalCount<T>(QueryOptions options) where T : IoTEntity
|
||||
private async Task<int> GetTotalCount<T>(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
|
||||
/// <param name="dataSet"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<T> ParseResults<T>(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new()
|
||||
private async Task<IEnumerable<T>> ParseResults<T>(SessionDataSet dataSet, int pageSize) where T : IoTEntity, new()
|
||||
{
|
||||
var results = new List<T>();
|
||||
var metadata = GetMetadata<T>();
|
||||
var metadata = await GetMetadata<T>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备元数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
private DeviceMetadata GetMetadata<T>() 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;
|
||||
//});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备元数据的列
|
||||
/// </summary>
|
||||
@ -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<TAGColumnAttribute>() is not null ? new ColumnInfo(
|
||||
name: prop.Name,
|
||||
category: ColumnCategory.TAG,
|
||||
dataType: GetDataTypeFromTypeName(prop.PropertyType.Name),
|
||||
dataType: GetDataTypeFromTypeName(typeName),
|
||||
false
|
||||
) : prop.GetCustomAttribute<ATTRIBUTEColumnAttribute>() is not null ? new ColumnInfo(
|
||||
prop.Name,
|
||||
ColumnCategory.ATTRIBUTE,
|
||||
GetDataTypeFromTypeName(prop.PropertyType.Name),
|
||||
GetDataTypeFromTypeName(typeName),
|
||||
false
|
||||
) : prop.GetCustomAttribute<FIELDColumnAttribute>() 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
|
||||
/// <summary>
|
||||
/// 构建设备元数据
|
||||
/// </summary>
|
||||
/// <param name="columns"></param>
|
||||
/// <param name="typeInfo">待解析的类</param>
|
||||
/// <param name="columns">已处理好的数据列</param>
|
||||
/// <returns></returns>
|
||||
private DeviceMetadata BuildDeviceMetadata(List<ColumnInfo> columns)
|
||||
private DeviceMetadata BuildDeviceMetadata<T>(List<ColumnInfo> 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<EntityTypeAttribute>();
|
||||
|
||||
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",
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
using JiShe.CollectBus.IoTDB.Attribute;
|
||||
|
||||
namespace JiShe.CollectBus.IoTDB.Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// IoT实体基类
|
||||
/// </summary>
|
||||
public abstract class IoTEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统名称
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string SystemName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 项目编码
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string ProjectCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型集中器、电表、水表、流量计、传感器等
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备ID
|
||||
/// </summary>
|
||||
[TAGColumn]
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前时间戳,单位毫秒
|
||||
/// </summary>
|
||||
public long Timestamps { get; set; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -57,5 +57,5 @@ public class KafkaOptionConfig
|
||||
/// <summary>
|
||||
/// 首次采集时间
|
||||
/// </summary>
|
||||
public DateTime FirstCollectionTime { get; set; }
|
||||
public DateTime? FirstCollectionTime { get; set; }
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<BaseProtocolPlugin> _logger;
|
||||
private readonly ILogger<BaseProtocolPlugin_bak> _logger;
|
||||
private readonly IRepository<ProtocolInfo, Guid> _protocolInfoRepository;
|
||||
private readonly IFreeRedisProvider _redisProvider;
|
||||
|
||||
@ -32,12 +32,13 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
||||
public const string errorData = "EE";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseProtocolPlugin"/> class.
|
||||
/// Initializes a new instance of the <see cref="BaseProtocolPlugin_bak"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The service provider.</param>
|
||||
protected BaseProtocolPlugin(IServiceProvider serviceProvider)
|
||||
protected BaseProtocolPlugin_bak(IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = serviceProvider.GetRequiredService<ILogger<BaseProtocolPlugin>>();
|
||||
|
||||
_logger = serviceProvider.GetRequiredService<ILogger<BaseProtocolPlugin_bak>>();
|
||||
_protocolInfoRepository = serviceProvider.GetRequiredService<IRepository<ProtocolInfo, Guid>>();
|
||||
_producerService = serviceProvider.GetRequiredService<IProducerService>();
|
||||
_redisProvider = serviceProvider.GetRequiredService<IFreeRedisProvider>();
|
||||
@ -607,149 +608,149 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
||||
var time = Appendix.Appendix_A1(hexDatas.Take(6).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用解析
|
||||
/// </summary>
|
||||
/// <param name="messageReceived"></param>
|
||||
/// <param name="sendAction"></param>
|
||||
/// <returns></returns>
|
||||
public virtual TB3761 AnalyzeReadingDataAsync(MessageReceived messageReceived,
|
||||
Action<byte[]>? sendAction = null)
|
||||
{
|
||||
var hexStringList = messageReceived.MessageHexString.StringToPairs();
|
||||
var afn = (AFN)hexStringList.GetAnalyzeValue(CommandChunkEnum.AFN);
|
||||
var fn = (int)hexStringList.GetAnalyzeValue(CommandChunkEnum.FN);
|
||||
///// <summary>
|
||||
///// 通用解析
|
||||
///// </summary>
|
||||
///// <param name="messageReceived"></param>
|
||||
///// <param name="sendAction"></param>
|
||||
///// <returns></returns>
|
||||
//public virtual TB3761 AnalyzeReadingDataAsync(MessageReceived messageReceived,
|
||||
// Action<byte[]>? 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<string>)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data);
|
||||
// var analyzeValue = (List<string>)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;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 通用解析 日冻结曲线类
|
||||
/// </summary>
|
||||
/// <param name="messageReceived"></param>
|
||||
/// <param name="sendAction"></param>
|
||||
/// <returns></returns>
|
||||
public virtual TB3761 AnalyzeReadingTdcDataAsync(MessageReceived messageReceived,
|
||||
Action<byte[]>? sendAction = null)
|
||||
{
|
||||
///// <summary>
|
||||
///// 通用解析 日冻结曲线类
|
||||
///// </summary>
|
||||
///// <param name="messageReceived"></param>
|
||||
///// <param name="sendAction"></param>
|
||||
///// <returns></returns>
|
||||
//public virtual TB3761 AnalyzeReadingTdcDataAsync(MessageReceived messageReceived,
|
||||
// Action<byte[]>? 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<string>)hexStringList.GetAnalyzeValue(CommandChunkEnum.Data);
|
||||
// var analyzeValue = (List<string>)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<string> analyzeValue, int dataIndex,int dataCount,string dataType)
|
||||
{
|
||||
@ -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<ProtocolInfo, Guid> _protocolInfoRepository;
|
||||
public ProtocolPlugin(IServiceProvider serviceProvider, ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_protocolInfoRepository = serviceProvider.GetRequiredService<IRepository<ProtocolInfo, Guid>>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public abstract ProtocolInfo Info { get; }
|
||||
|
||||
public virtual async Task<ProtocolInfo> 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<T> AnalyzeAsync<T>(ITcpSessionClient client, string messageReceived, Action<T>? receivedAction = null) where T :class;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解析376.1帧
|
||||
/// </summary>
|
||||
/// <param name="messageReceived"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控制域C解析
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual C? Analysis_C(List<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 地址域A解析
|
||||
/// </summary>
|
||||
/// <param name="hexStringList"></param>
|
||||
/// <returns></returns>
|
||||
public virtual A? Analysis_A(List<string> 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;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 站地址和组地址标志A3
|
||||
/// </summary>
|
||||
/// <param name="hexAList">地址域A集合</param>
|
||||
/// <returns></returns>
|
||||
public virtual A3? Analysis_A3(List<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AFN_FC功能码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual AFN_FC? Analysis_AFN_FC(List<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析帧序列域SEQ
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual SEQ? Analysis_SEQ(List<string> 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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 数据单元标识及数据单元数据
|
||||
/// </summary>
|
||||
public virtual UnitData? Analysis_UnitData(List<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 信息点DA Pn
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual DA? Analysis_DA(List<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 信息类DT Fn
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual DT? Analysis_DT(List<string> 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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 计算Pn
|
||||
/// </summary>
|
||||
/// <param name="da1"></param>
|
||||
/// <param name="da2"></param>
|
||||
/// <returns></returns>
|
||||
public int CalculatePn(string da1, string da2) => (da2.HexToDec() - 1) * 8 + (8 - da1.HexTo4BinZero().IndexOf(da1.Equals("00") ? "0" : "1"));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 计算Fn
|
||||
/// </summary>
|
||||
/// <param name="dt1"></param>
|
||||
/// <param name="dt2"></param>
|
||||
/// <returns></returns>
|
||||
public int CalculateFn(string dt1, string dt2) => dt2.HexToDec() * 8 + (8 - dt1.HexTo4BinZero().IndexOf("1"));
|
||||
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 执行策略
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public Task<TResult> ExecuteAsync<TInput, TResult>(string type, TInput input)
|
||||
{
|
||||
var factory = _provider.GetRequiredService<Func<string, Type, Type, object>>();
|
||||
var strategy = (IAnalysisStrategy<TInput, TResult>)factory(type, typeof(TInput), typeof(TResult));
|
||||
return strategy.ExecuteAsync(input);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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<TInput, TResult>
|
||||
{
|
||||
Task<TResult> ExecuteAsync(TInput input);
|
||||
}
|
||||
}
|
||||
@ -14,10 +14,12 @@ namespace JiShe.CollectBus.Protocol.Contracts.Interfaces
|
||||
|
||||
Task LoadAsync();
|
||||
|
||||
Task<T> AnalyzeAsync<T>(MessageReceived messageReceived, Action<byte[]>? sendAction = null) where T : TB3761;
|
||||
Task<T> AnalyzeAsync<T>(ITcpSessionClient client, string messageReceived, Action<T>? 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 解析3761报文
|
||||
/// </summary>
|
||||
public class TB3761
|
||||
{
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// 报文
|
||||
/// </summary>
|
||||
public BaseHexMessage? BaseHexMessage { get; set;}
|
||||
|
||||
public AFN Afn { get; set; }
|
||||
/// <summary>
|
||||
/// 控制域C
|
||||
/// </summary>
|
||||
public C? C { get; set; }
|
||||
|
||||
public List<TB3761FN> FnList { get; set; }
|
||||
/// <summary>
|
||||
/// 地址域A
|
||||
/// </summary>
|
||||
public A? A { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 帧序列域 SEQ
|
||||
/// </summary>
|
||||
public SEQ? SEQ { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据区
|
||||
/// 功能码
|
||||
/// </summary>
|
||||
public AFN_FC? AFN_FC { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据区
|
||||
/// 信息点DA Pn
|
||||
/// </summary>
|
||||
public DA? DA { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据区
|
||||
/// 信息类DT Fn
|
||||
/// </summary>
|
||||
public DT? DT { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据单元标识和数据单元格式
|
||||
/// </summary>
|
||||
public UnitData? UnitData { get; set; }
|
||||
}
|
||||
|
||||
#region
|
||||
|
||||
/// <summary>
|
||||
/// 报文信息
|
||||
/// </summary>
|
||||
public class BaseHexMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 报文
|
||||
/// </summary>
|
||||
public string? HexMessageString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报文数组
|
||||
/// </summary>
|
||||
public List<string>? HexMessageList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控制域C
|
||||
/// </summary>
|
||||
public class C
|
||||
{
|
||||
/// <summary>
|
||||
/// 控制域C报文
|
||||
/// </summary>
|
||||
public BaseHexMessage? BaseHexMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 传输方向位D7 DIR=0,表示此帧报文是由主站发出的下行报文;DIR=1,表示此帧报文是由终端发出的上行报文。
|
||||
/// </summary>
|
||||
public int DIR { get; set; }
|
||||
/// <summary>
|
||||
/// D6启动标志位 0:表示此帧报文来自从动站(终端),1:表示此帧报文来自启动站(服务端)
|
||||
/// </summary>
|
||||
public int PRM { get; set; }
|
||||
/// <summary>
|
||||
/// D5下行:帧计数位(FCB)/上行(ACD):要求访问位(终端有重要事件等待访问),
|
||||
/// </summary>
|
||||
public int FCB { get; set; }
|
||||
/// <summary>
|
||||
/// 下行:帧计数有效位(决定FCB位有效/无效)/上行:保留 D4
|
||||
/// </summary>
|
||||
public int FCV { get; set; }
|
||||
/// <summary>
|
||||
/// 功能码 D0-D3
|
||||
/// </summary>
|
||||
public int FC { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TB3761FN
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 地址域A
|
||||
/// </summary>
|
||||
public class A
|
||||
{
|
||||
/// <summary>
|
||||
/// 地址域报文
|
||||
/// </summary>
|
||||
public BaseHexMessage? BaseHexMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 集中器/终端编码
|
||||
/// </summary>
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 行政区划码A1
|
||||
/// </summary>
|
||||
public string? A1 { get; set; }
|
||||
/// <summary>
|
||||
/// 终端地址A2
|
||||
/// </summary>
|
||||
public int A2 { get; set; }
|
||||
/// <summary>
|
||||
/// 站地址和组地址标志A3
|
||||
/// </summary>
|
||||
public A3? A3 { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 站地址和组地址标志A3
|
||||
/// </summary>
|
||||
public class A3
|
||||
{
|
||||
/// <summary>
|
||||
/// 地址域A3报文
|
||||
/// </summary>
|
||||
public BaseHexMessage? BaseHexMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 终端组地址标志,D0=0即False 表示终端地址A2 为单地址
|
||||
/// </summary>
|
||||
public int D0 { get; set; }
|
||||
/// <summary>
|
||||
/// 主站地址 MSA D1~D7 组成 0~127
|
||||
/// </summary>
|
||||
public int D1_D7 { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 帧序列域 SEQ
|
||||
/// </summary>
|
||||
public class SEQ
|
||||
{
|
||||
/// <summary>
|
||||
/// 帧序列域报文
|
||||
/// </summary>
|
||||
public BaseHexMessage? BaseHexMessage { get; set; }
|
||||
/// <summary>
|
||||
/// 响应帧序号
|
||||
/// </summary>
|
||||
public int PSEQ { get; set; }
|
||||
/// <summary>
|
||||
/// CON为“1”,表示需要对该帧报文进行确认;置“0”,表示不需要对该帧报文进行确认。
|
||||
/// </summary>
|
||||
public int CON { get; set; }
|
||||
/// <summary>
|
||||
/// 末帧标志
|
||||
/// </summary>
|
||||
public int FIN { get; set; }
|
||||
/// <summary>
|
||||
/// 首帧标志
|
||||
/// </summary>
|
||||
public int FIR { get; set; }
|
||||
/// <summary>
|
||||
/// 帧时间标签有效位,TpV=0,表示在附加信息域中无时间标签Tp;TpV=1,表示在附加信息域中带有时间标签Tp
|
||||
/// </summary>
|
||||
public int TpV { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据区
|
||||
/// 功能码
|
||||
/// </summary>
|
||||
public class AFN_FC
|
||||
{
|
||||
public BaseHexMessage? BaseHexMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能码
|
||||
/// </summary>
|
||||
public int AFN { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据区
|
||||
/// 信息点DA Pn
|
||||
/// </summary>
|
||||
public class DA
|
||||
{
|
||||
public BaseHexMessage? BaseHexMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 信息点 DA Pn
|
||||
/// </summary>
|
||||
public int Pn { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 用户数据区
|
||||
/// 信息类DT Fn
|
||||
/// </summary>
|
||||
public class DT
|
||||
{
|
||||
public BaseHexMessage? BaseHexMessage { get; set; }
|
||||
/// <summary>
|
||||
/// 信息类 DT Fn
|
||||
/// </summary>
|
||||
public int Fn { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
|
||||
public List<TB3761UP> UpList { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TB3761UP
|
||||
|
||||
/// <summary>
|
||||
/// 数据单元标识和数据单元格式
|
||||
/// </summary>
|
||||
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<TB3761UP> Tb3761UpChildlList { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 集中器地址
|
||||
/// </summary>
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// AFN功能码
|
||||
/// </summary>
|
||||
public int AFN { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 信息点
|
||||
/// </summary>
|
||||
public int Pn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 信息类
|
||||
/// </summary>
|
||||
public int Fn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据时标(最近数据时间点的时间),如:8:00 08:15 记录08:15
|
||||
/// </summary>
|
||||
public string? DataTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密度(分)
|
||||
/// </summary>
|
||||
public int TimeDensity { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<TestProtocolPlugin> _logger;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TestProtocolPlugin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The service provider.</param>
|
||||
public TestProtocolPlugin(IServiceProvider serviceProvider) : base(serviceProvider)
|
||||
public TestProtocolPlugin(IServiceProvider serviceProvider, ILogger<TestProtocolPlugin> logger) : base(serviceProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public sealed override ProtocolInfo Info => new(nameof(TestProtocolPlugin), "Test", "TCP", "Test协议", "DTS1980-Test");
|
||||
|
||||
public override Task<T> AnalyzeAsync<T>(MessageReceived messageReceived, Action<byte[]>? sendAction = null)
|
||||
public override Task<T> AnalyzeAsync<T>(ITcpSessionClient client, string messageReceived, Action<T>? receivedAction = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@ -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<TB3761, AFN0_F1_AnalysisDto>
|
||||
{
|
||||
private readonly ILogger<AFN0_F1_Analysis> _logger;
|
||||
|
||||
public AFN0_F1_Analysis(ILogger<AFN0_F1_Analysis> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<AFN0_F1_AnalysisDto> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<IProtocolPlugin, StandardProtocolPlugin>(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<IProtocolPlugin>(nameof(StandardProtocolPlugin));
|
||||
await standardProtocol.LoadAsync();
|
||||
}
|
||||
|
||||
public void RegisterProtocolAnalysis(IServiceCollection services)
|
||||
{
|
||||
// 扫描并注册所有策略
|
||||
var strategyMetadata = new Dictionary<(string, Type, Type), Type>();
|
||||
services.AddTransient<AnalysisStrategyContext>();
|
||||
|
||||
// 批量注册
|
||||
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<Func<string, Type, Type, object?>>(provider => (name, inputType, resultType) =>
|
||||
{
|
||||
var metadata = provider.GetRequiredService<Dictionary<(string, Type, Type), Type>>();
|
||||
if (metadata.TryGetValue((name, inputType, resultType), out var strategyType))
|
||||
{
|
||||
return provider.GetRequiredService(strategyType);
|
||||
}
|
||||
else
|
||||
{
|
||||
var logger= provider.GetRequiredService<ILogger<AnalysisStrategyContext>>();
|
||||
logger.LogWarning($"未能找到解析策略:{name}-{inputType}-{resultType}");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<StandardProtocolPlugin> _logger;
|
||||
|
||||
private readonly IProducerService _producerService;
|
||||
|
||||
private readonly IRepository<Device, Guid> _deviceRepository;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StandardProtocolPlugin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The service provider.</param>
|
||||
public StandardProtocolPlugin(IServiceProvider serviceProvider) : base(serviceProvider)
|
||||
public StandardProtocolPlugin(IServiceProvider serviceProvider,ILogger<StandardProtocolPlugin> logger) : base(serviceProvider, logger)
|
||||
{
|
||||
_logger= logger;
|
||||
//_logger = serviceProvider.GetRequiredService<ILogger<StandardProtocolPlugin>>();
|
||||
_producerService = serviceProvider.GetRequiredService<IProducerService>();
|
||||
_deviceRepository = serviceProvider.GetRequiredService<IRepository<Device, Guid>>();
|
||||
}
|
||||
|
||||
public sealed override ProtocolInfo Info => new(nameof(StandardProtocolPlugin), "376.1", "TCP", "376.1协议", "DTS1980");
|
||||
|
||||
public override async Task<T> AnalyzeAsync<T>(MessageReceived messageReceived, Action<byte[]>? sendAction = null)
|
||||
public override async Task<T> AnalyzeAsync<T>(ITcpSessionClient client, string messageReceived, Action<T>? sendAction = null)
|
||||
{
|
||||
var hexStringList = messageReceived.MessageHexString.StringToPairs();
|
||||
var aTuple = (Tuple<string, int>)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)!;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 登录回复
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="msa"></param>
|
||||
/// <param name="pseq"></param>
|
||||
/// <returns></returns>
|
||||
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<string, int>)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 });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 心跳帧解析
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="msa"></param>
|
||||
/// <param name="pseq"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
|
||||
@ -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<TcpMonitor> _logger;
|
||||
private readonly IRepository<Device, Guid> _deviceRepository;
|
||||
private readonly IDistributedCache<AmmeterInfo> _ammeterInfoCache;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="producerService"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="deviceRepository"></param>
|
||||
/// <param name="ammeterInfoCache"></param>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="producerService"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="deviceRepository"></param>
|
||||
/// <param name="ammeterInfoCache"></param>
|
||||
/// <param name="serviceProvider"></param>
|
||||
public TcpMonitor(IProducerService producerService,
|
||||
ILogger<TcpMonitor> logger,
|
||||
IRepository<Device, Guid> deviceRepository,
|
||||
IDistributedCache<AmmeterInfo> ammeterInfoCache)
|
||||
IDistributedCache<AmmeterInfo> 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<string, int>)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<IProtocolPlugin>("StandardProtocolPlugin");
|
||||
if (protocolPlugin == null)
|
||||
{
|
||||
_logger.LogError("协议不存在!");
|
||||
}
|
||||
else
|
||||
|
||||
TB3761? tB3761 = await protocolPlugin!.AnalyzeAsync<TB3761>(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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录帧处理
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="messageHexString"></param>
|
||||
/// <param name="deviceNo">集中器编号</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 正常帧处理,将不同的AFN进行分发
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="tcpSessionClient"></param>
|
||||
/// <param name="messageHexString"></param>
|
||||
/// <param name="deviceNo"></param>
|
||||
/// <param name="aFn"></param>
|
||||
/// <param name="tB3761"></param>
|
||||
/// <returns></returns>
|
||||
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<IProtocolPlugin>("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<string> 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()
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,8 @@ namespace JiShe.CollectBus.RedisDataCache
|
||||
Instance = _freeRedisProvider.Instance;
|
||||
}
|
||||
|
||||
//todo 单个数据查询
|
||||
|
||||
/// <summary>
|
||||
/// 单个添加数据
|
||||
/// </summary>
|
||||
|
||||
@ -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<SampleAppService> _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<IoTDbOptions> options,
|
||||
IoTDbRuntimeContext dbContext, ILogger<SampleAppService> logger, IRedisDataCacheService redisDataCacheService)
|
||||
IoTDBRuntimeContext dbContext, ILogger<SampleAppService> logger, IRedisDataCacheService redisDataCacheService)
|
||||
{
|
||||
_iotDBProvider = iotDBProvider;
|
||||
_options = options.Value;
|
||||
@ -52,33 +48,23 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS
|
||||
/// <summary>
|
||||
/// 测试 UseSessionPool
|
||||
/// </summary>
|
||||
/// <param name="timestamps"></param>
|
||||
/// <param name="testTime"></param>
|
||||
/// <returns></returns>
|
||||
[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
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[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);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试Session切换3
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试树模型单个测点数据项
|
||||
/// </summary>
|
||||
/// <param name="measuring"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task TestTreeModelSingleMeasuringEntity(string measuring, string value, DateTime time)
|
||||
{
|
||||
var meter = new TreeModelSingleMeasuringEntity<string>()
|
||||
{
|
||||
SystemName = "energy",
|
||||
DeviceId = "402440506",
|
||||
DeviceType = "Ammeter",
|
||||
ProjectId = "10059",
|
||||
Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(),
|
||||
SingleMeasuring = new Tuple<string, string>(measuring, value)
|
||||
};
|
||||
await _iotDBProvider.InsertAsync(meter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试树模型单个测点数据项2
|
||||
/// </summary>
|
||||
/// <param name="measuring"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task TestTreeModelSingleMeasuringEntity2(string measuring, int value, DateTime time)
|
||||
{
|
||||
var meter = new TreeModelSingleMeasuringEntity<int>()
|
||||
{
|
||||
SystemName = "energy",
|
||||
DeviceId = "402440506",
|
||||
DeviceType = "Ammeter",
|
||||
ProjectId = "10059",
|
||||
Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(),
|
||||
SingleMeasuring = new Tuple<string, int>(measuring, value)
|
||||
};
|
||||
await _iotDBProvider.InsertAsync(meter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试表模型单个测点数据项
|
||||
/// </summary>
|
||||
/// <param name="measuring"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task TestTableModelSingleMeasuringEntity(string measuring, string value, DateTime time)
|
||||
{
|
||||
var meter = new TableModelSingleMeasuringEntity<string>()
|
||||
{
|
||||
SystemName = "energy",
|
||||
DeviceId = "402440506",
|
||||
DeviceType = "Ammeter",
|
||||
ProjectId = "10059",
|
||||
Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(),
|
||||
SingleColumn = new Tuple<string, string>(measuring, value)
|
||||
};
|
||||
_dbContext.UseTableSessionPool = true;
|
||||
await _iotDBProvider.InsertAsync(meter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试表模型单个测点数据项2
|
||||
/// </summary>
|
||||
/// <param name="measuring"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task TestTableModelSingleMeasuringEntity2(string measuring, int value, DateTime time)
|
||||
{
|
||||
var meter = new TableModelSingleMeasuringEntity<int>()
|
||||
{
|
||||
SystemName = "energy",
|
||||
DeviceId = "402440506",
|
||||
DeviceType = "Ammeter",
|
||||
ProjectId = "10059",
|
||||
Timestamps = time.GetDateTimeOffset().ToUnixTimeMilliseconds(),
|
||||
SingleColumn = new Tuple<string, int>(measuring, value)
|
||||
};
|
||||
_dbContext.UseTableSessionPool = true;
|
||||
await _iotDBProvider.InsertAsync(meter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -172,26 +290,6 @@ public class SampleAppService : CollectBusAppService, ISampleAppService, IKafkaS
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 测试单个测点数据项
|
||||
/// </summary>
|
||||
/// <param name="measuring"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task TestSingleMeasuringAFNData(string measuring, string value)
|
||||
{
|
||||
var meter = new SingleMeasuringAFNDataEntity<string>()
|
||||
{
|
||||
SystemName = "energy",
|
||||
DeviceId = "402440506",
|
||||
DeviceType = "Ammeter",
|
||||
ProjectCode = "10059",
|
||||
Timestamps = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
SingleMeasuring = new Tuple<string, string>(measuring, value)
|
||||
};
|
||||
await _iotDBProvider.InsertAsync(meter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试Redis批量读取10万条数据性能
|
||||
/// </summary>
|
||||
|
||||
@ -12,7 +12,6 @@ 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.Protocol.Contracts.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using JiShe.CollectBus.Cassandra;
|
||||
|
||||
@ -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<BasicScheduledMeterReadingService> _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<BasicScheduledMeterReadingService> logger,
|
||||
IMeterReadingRecordRepository meterReadingRecordRepository,
|
||||
IProducerService producerService,
|
||||
IRedisDataCacheService redisDataCacheService,
|
||||
IIoTDbProvider dbProvider,
|
||||
IoTDBRuntimeContext runtimeContext,
|
||||
IOptions<KafkaOptionConfig> 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<MeterReadingTelemetryPacketInfo> pushTaskInfos = new();
|
||||
_runtimeContext.UseTableSessionPool = true;
|
||||
var metadata = await _dbProvider.GetMetadata<MeterReadingTelemetryPacketInfo>();
|
||||
_ = CreateMeterPublishTask<AmmeterInfo>(
|
||||
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<WatermeterInfo>(
|
||||
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);
|
||||
}
|
||||
@ -194,10 +209,6 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
||||
public virtual async Task InitAmmeterCacheData(string gatherCode = "")
|
||||
{
|
||||
#if DEBUG
|
||||
return;
|
||||
|
||||
|
||||
|
||||
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);
|
||||
//});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -443,7 +459,17 @@ 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<TasksToBeIssueModel>(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<QueryCondition>();
|
||||
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<MeterReadingTelemetryPacketInfo>(timeDensity, new IoTDBQueryOptions()
|
||||
{
|
||||
TableNameOrTreePath = DevicePathBuilder.GetTableName<MeterReadingTelemetryPacketInfo>(),
|
||||
PageIndex = 1,
|
||||
PageSize = 3000,
|
||||
Conditions = conditions,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建电表待发送的任务数据
|
||||
/// </summary>
|
||||
/// <param name="timeDensity">采集频率</param>
|
||||
/// <param name="taskBatch">时间格式的任务批次名称</param>
|
||||
/// <returns></returns>
|
||||
private async Task AmmerterCreatePublishTask(int timeDensity, string taskBatch)
|
||||
{
|
||||
var timer = Stopwatch.StartNew();
|
||||
///// <summary>
|
||||
///// 创建电表待发送的任务数据
|
||||
///// </summary>
|
||||
///// <param name="timeDensity">采集频率</param>
|
||||
///// <param name="taskBatch">时间格式的任务批次名称</param>
|
||||
///// <returns></returns>
|
||||
//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<AmmeterInfo> meterInfos = new List<AmmeterInfo>();
|
||||
decimal? cursor = null;
|
||||
string member = null;
|
||||
bool hasNext;
|
||||
do
|
||||
{
|
||||
var page = await _redisDataCacheService.GetAllPagedData<AmmeterInfo>(
|
||||
redisCacheMeterInfoHashKeyTemp,
|
||||
redisCacheMeterInfoZSetScoresIndexKeyTemp,
|
||||
pageSize: 1000,
|
||||
lastScore: cursor,
|
||||
lastMember: member);
|
||||
// List<AmmeterInfo> meterInfos = new List<AmmeterInfo>();
|
||||
// decimal? cursor = null;
|
||||
// string member = null;
|
||||
// bool hasNext;
|
||||
// do
|
||||
// {
|
||||
// var page = await _redisDataCacheService.GetAllPagedData<AmmeterInfo>(
|
||||
// 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}表计信息");
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
@ -527,38 +561,33 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
||||
/// <param name="timeDensity">采集频率</param>
|
||||
/// <param name="ammeterInfo">电表信息</param>
|
||||
/// <param name="groupIndex">集中器所在分组</param>
|
||||
/// <param name="taskBatch">时间格式的任务批次名称</param>
|
||||
/// <param name="timestamps">采集频率对应的时间戳</param>
|
||||
/// <returns></returns>
|
||||
private void AmmerterCreatePublishTaskAction(int timeDensity
|
||||
, AmmeterInfo ammeterInfo, int groupIndex, string taskBatch)
|
||||
private List<MeterReadingTelemetryPacketInfo> 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<string> tempCodes = ammeterInfo.ItemCodes.Deserialize<List<string>>()!;
|
||||
@ -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}分钟采集水表数据处理完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建水表待发送的任务数据
|
||||
/// </summary>
|
||||
/// <param name="timeDensity">采集频率</param>
|
||||
/// <param name="meterInfo">水表信息</param>
|
||||
/// <param name="groupIndex">集中器所在分组</param>
|
||||
/// <param name="taskBatch">时间格式的任务批次名称</param>
|
||||
/// <returns></returns>
|
||||
private void WatermeterCreatePublishTaskAction(int timeDensity
|
||||
, WatermeterInfo meterInfo, int groupIndex, string taskBatch)
|
||||
{
|
||||
var handlerPacketBuilder = TelemetryPacketBuilder.AFNHandlersDictionary;
|
||||
///// <summary>
|
||||
///// 创建水表待发送的任务数据
|
||||
///// </summary>
|
||||
///// <param name="timeDensity">采集频率</param>
|
||||
///// <param name="meterInfo">水表信息</param>
|
||||
///// <param name="groupIndex">集中器所在分组</param>
|
||||
///// <param name="taskBatch">时间格式的任务批次名称</param>
|
||||
///// <returns></returns>
|
||||
//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);
|
||||
// Build188SendData.Build188WaterMeterReadingSendDataUnit(meterInfo.Address);
|
||||
|
||||
using (var pipe = FreeRedisProvider.Instance.StartPipe())
|
||||
{
|
||||
// 主数据存储Hash
|
||||
pipe.HSet(redisCacheTelemetryPacketInfoHashKey, taskInfo.MemberId, taskInfo.Serialize());
|
||||
// using (var pipe = FreeRedisProvider.Instance.StartPipe())
|
||||
// {
|
||||
// // 主数据存储Hash
|
||||
// pipe.HSet(redisCacheTelemetryPacketInfoHashKey, taskInfo.MemberId, taskInfo.Serialize());
|
||||
|
||||
// Set索引缓存
|
||||
pipe.SAdd(redisCacheTelemetryPacketInfoSetIndexKey, taskInfo.MemberId);
|
||||
// // Set索引缓存
|
||||
// pipe.SAdd(redisCacheTelemetryPacketInfoSetIndexKey, taskInfo.MemberId);
|
||||
|
||||
// ZSET索引缓存Key
|
||||
pipe.ZAdd(redisCacheTelemetryPacketInfoZSetScoresIndexKey, taskInfo.ScoreValue, taskInfo.MemberId);
|
||||
// // ZSET索引缓存Key
|
||||
// pipe.ZAdd(redisCacheTelemetryPacketInfoZSetScoresIndexKey, taskInfo.ScoreValue, taskInfo.MemberId);
|
||||
|
||||
pipe.EndPipe();
|
||||
}
|
||||
// pipe.EndPipe();
|
||||
// }
|
||||
|
||||
}
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -961,11 +960,11 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
||||
/// 创建表的待发送的任务数据
|
||||
/// </summary>
|
||||
/// <param name="timeDensity">采集频率</param>
|
||||
/// <param name="taskBatch">时间格式的任务批次名称</param>
|
||||
/// <param name="nextTaskTime">采集频率对应的任务时间戳</param>
|
||||
/// <param name="meterType">表类型</param>
|
||||
/// <param name="taskCreateAction">具体的创建任务的委托</param>
|
||||
/// <returns></returns>
|
||||
private async Task CreateMeterPublishTask<T>(int timeDensity, string taskBatch, MeterTypeEnum meterType, Action<int, T, int, string> taskCreateAction) where T : DeviceCacheBasicModel
|
||||
private async Task CreateMeterPublishTask<T>(int timeDensity, DateTime nextTaskTime, MeterTypeEnum meterType, Action<int, T, int, DateTime> 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<T>(
|
||||
redisCacheMeterInfoHashKeyTemp,
|
||||
redisCacheMeterInfoZSetScoresIndexKeyTemp,
|
||||
pageSize: 1000,
|
||||
lastScore: cursor,
|
||||
lastMember: member);
|
||||
//do
|
||||
//{
|
||||
// var page = await _redisDataCacheService.GetAllPagedData<T>(
|
||||
// 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<T>(
|
||||
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}表计信息");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建Kafka消息
|
||||
/// </summary>
|
||||
/// <param name="redisCacheTelemetryPacketInfoHashKey"></param>
|
||||
/// <param name="redisCacheTelemetryPacketInfoZSetScoresIndexKey"></param>
|
||||
/// <returns></returns>
|
||||
private async Task CreateMeterKafkaTaskMessage(
|
||||
string redisCacheTelemetryPacketInfoHashKey,
|
||||
string redisCacheTelemetryPacketInfoZSetScoresIndexKey)
|
||||
private async Task CreateMeterKafkaTaskMessage<T>(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<MeterReadingTelemetryPacketInfo>(
|
||||
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<T>(options);
|
||||
|
||||
await DeviceGroupBalanceControl.ProcessWithThrottleAsync(
|
||||
items: page.Items,
|
||||
deviceIdSelector: data => data.FocusAddress,
|
||||
hasNext = pageResult.HasNext;
|
||||
|
||||
await DeviceGroupBalanceControl.ProcessWithThrottleAsync<T>(
|
||||
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}毫秒。");
|
||||
}
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// 创建Kafka消息
|
||||
///// </summary>
|
||||
///// <param name="redisCacheTelemetryPacketInfoHashKey"></param>
|
||||
///// <param name="redisCacheTelemetryPacketInfoZSetScoresIndexKey"></param>
|
||||
///// <returns></returns>
|
||||
//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<MeterReadingTelemetryPacketInfo>(
|
||||
// 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}毫秒。");
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Kafka 推送消息
|
||||
/// </summary>
|
||||
@ -1069,15 +1109,15 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
||||
/// <param name="taskRecord">任务记录</param>
|
||||
/// <param name="partition">对应分区,也就是集中器号所在的分组序号</param>
|
||||
/// <returns></returns>
|
||||
private async Task KafkaProducerIssuedMessageAction(string topicName,
|
||||
MeterReadingTelemetryPacketInfo taskRecord, int partition)
|
||||
private async Task KafkaProducerIssuedMessageAction<T>(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<T>(topicName, taskRecord, partition);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -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<EnergySystemScheduledMeterReadingService> logger,
|
||||
IIoTDbProvider dbProvider,
|
||||
IMeterReadingRecordRepository meterReadingRecordRepository,
|
||||
IOptions<KafkaOptionConfig> kafkaOptions,
|
||||
IoTDBRuntimeContext runtimeContext,
|
||||
IProducerService producerService,
|
||||
IRedisDataCacheService redisDataCacheService)
|
||||
: base(logger,
|
||||
meterReadingRecordRepository,
|
||||
producerService,
|
||||
redisDataCacheService,
|
||||
dbProvider,
|
||||
runtimeContext,
|
||||
kafkaOptions)
|
||||
{
|
||||
serverTagName = kafkaOptions.Value.ServerTagName;
|
||||
|
||||
@ -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
|
||||
{
|
||||
@ -67,33 +70,23 @@ namespace JiShe.CollectBus.Subscribers
|
||||
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<TB3761>(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<ISubscribeAck> ReceivedHeartbeatEvent(List<MessageReceivedHeartbeat> receivedHeartbeatMessages)
|
||||
{
|
||||
foreach (var receivedHeartbeatMessage in receivedHeartbeatMessages)
|
||||
{
|
||||
var protocolPlugin = _serviceProvider.GetKeyedService<IProtocolPlugin>("StandardProtocolPlugin");
|
||||
if (protocolPlugin == null)
|
||||
{
|
||||
_logger.LogError("协议不存在!");
|
||||
}
|
||||
else
|
||||
{
|
||||
await protocolPlugin.HeartbeatAsync(receivedHeartbeatMessage);
|
||||
await _messageReceivedHeartbeatEventRepository.InsertAsync(receivedHeartbeatMessage);
|
||||
}
|
||||
}
|
||||
//foreach (var receivedHeartbeatMessage in receivedHeartbeatMessages)
|
||||
//{
|
||||
// var protocolPlugin = _serviceProvider.GetKeyedService<IProtocolPlugin>("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<ISubscribeAck> ReceivedLoginEvent(List<MessageReceivedLogin> receivedLoginMessages)
|
||||
{
|
||||
foreach (var receivedLoginMessage in receivedLoginMessages)
|
||||
//foreach (var receivedLoginMessage in receivedLoginMessages)
|
||||
//{
|
||||
//var protocolPlugin = _serviceProvider.GetKeyedService<IProtocolPlugin>("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<ISubscribeAck> ReceivedAFN00Event(MessageReceived receivedMessage)
|
||||
{
|
||||
|
||||
var protocolPlugin = _serviceProvider.GetKeyedService<IProtocolPlugin>("StandardProtocolPlugin");
|
||||
if (protocolPlugin == null)
|
||||
{
|
||||
var protocolPlugin = _serviceProvider.GetKeyedService<IProtocolPlugin>("StandardProtocolPlugin");
|
||||
if (protocolPlugin == null)
|
||||
_logger.LogError("协议不存在!");
|
||||
}
|
||||
else
|
||||
{
|
||||
TB3761? tB3761 = protocolPlugin.Analysis3761(receivedMessage.MessageHexString);
|
||||
if (tB3761 == null)
|
||||
{
|
||||
_logger.LogError("协议不存在!");
|
||||
Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}");
|
||||
return SubscribeAck.Success();
|
||||
}
|
||||
else
|
||||
if (tB3761.DT == null || tB3761.AFN_FC == null)
|
||||
{
|
||||
await protocolPlugin.LoginAsync(receivedLoginMessage);
|
||||
await _messageReceivedLoginEventRepository.InsertAsync(receivedLoginMessage);
|
||||
Logger.LogError($"数据解析失败:{receivedMessage.Serialize()}");
|
||||
return SubscribeAck.Success();
|
||||
}
|
||||
string serverName = $"AFN{tB3761.AFN_FC.AFN}_F{tB3761.DT.Fn}_Analysis";
|
||||
//var analysisStrategy = _serviceProvider.GetKeyedService<IAnalysisStrategy>($"AFN0_F1_Analysis");
|
||||
|
||||
//var data = await analysisStrategy.ExecuteAsync<UnitDataDto<AFN0_F1_AnalysisDto>>(tB3761);
|
||||
var executor = _serviceProvider.GetRequiredService<AnalysisStrategyContext>();
|
||||
AFN0_F1_AnalysisDto aFN0_F1_AnalysisDto= await executor.ExecuteAsync<TB3761,AFN0_F1_AnalysisDto>("AFN0_F1_Analysis", tB3761);
|
||||
}
|
||||
|
||||
return SubscribeAck.Success();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ namespace JiShe.CollectBus.Ammeters
|
||||
/// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义
|
||||
/// </summary>
|
||||
[Column(IsIgnore = true)]
|
||||
public override string MemberId => $"{FocusId}:{MeterId}";
|
||||
public override string MemberId => $"{FocusAddress}:{MeteringCode}";
|
||||
|
||||
/// <summary>
|
||||
/// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// 下发消息内容
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public string IssuedMessageHexString { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// 下发消息Id
|
||||
///// </summary>
|
||||
//[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; }
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
/// <summary>
|
||||
/// 抄读任务Redis缓存数据记录
|
||||
/// </summary>
|
||||
public class MeterReadingTelemetryPacketInfo : DeviceCacheBasicModel
|
||||
[EntityType(EntityTypeEnum.TableModel)]
|
||||
public class MeterReadingTelemetryPacketInfo : IoTEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 关系映射标识,用于ZSet的Member字段和Set的Value字段,具体值可以根据不同业务场景进行定义
|
||||
/// 排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳、或者某一个固定的标识1
|
||||
/// </summary>
|
||||
public override string MemberId => $"{FocusId}:{MeterId}:{ItemCode}";
|
||||
|
||||
/// <summary>
|
||||
/// ZSet排序索引分数值,具体值可以根据不同业务场景进行定义,例如时间戳
|
||||
/// </summary>
|
||||
public override long ScoreValue => ((long)FocusId << 32) | (uint)DateTime.Now.Ticks;
|
||||
|
||||
[FIELDColumn]
|
||||
public string ScoreValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"{DeviceId}.{TaskMark}".Md5Fun();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否手动操作
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public bool ManualOrNot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务数据唯一标记
|
||||
/// </summary>
|
||||
public decimal TaskMark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间戳标记,IoTDB时间列处理,上报通过构建标记获取唯一标记匹配时间戳。
|
||||
/// </summary>
|
||||
public long Timestamps { get; set; }
|
||||
[FIELDColumn]
|
||||
public string TaskMark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否超时
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public bool IsTimeout { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 待抄读时间
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public DateTime PendingCopyReadTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 集中器Id
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public int FocusId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 集中器地址
|
||||
/// 表Id
|
||||
/// </summary>
|
||||
public string FocusAddress { get; set; }
|
||||
[FIELDColumn]
|
||||
public int MeterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表地址
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public string MeterAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表类型
|
||||
/// </summary>
|
||||
public MeterTypeEnum MeterType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 项目ID
|
||||
/// </summary>
|
||||
public int ProjectID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库业务ID
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public int DatabaseBusiID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// AFN功能码
|
||||
/// </summary>
|
||||
public AFN AFN { get; set; }
|
||||
[FIELDColumn]
|
||||
public int AFN { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 抄读功能码
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public int Fn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -95,66 +100,73 @@ namespace JiShe.CollectBus.IotSystems.MeterReadingRecords
|
||||
/// <summary>
|
||||
/// 采集项编码
|
||||
/// </summary>
|
||||
public string ItemCode { get; set;}
|
||||
[FIELDColumn]
|
||||
public string ItemCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 帧序列域SEQ
|
||||
/// </summary>
|
||||
public required Seq Seq { get; set; }
|
||||
///// <summary>
|
||||
///// 帧序列域SEQ
|
||||
///// </summary>
|
||||
//public required Seq Seq { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 地址域A3的主站地址MSA
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public int MSA { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否发送
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public bool IsSend { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public DateTime CreationTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下发消息内容
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public string IssuedMessageHexString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下发消息Id
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public string IssuedMessageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息上报内容
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public string? ReceivedMessageHexString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息上报时间
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public DateTime? ReceivedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上报消息Id
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public string ReceivedMessageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上报报文解析备注,异常情况下才有
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public string ReceivedRemark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已上报
|
||||
/// </summary>
|
||||
[FIELDColumn]
|
||||
public bool IsReceived { get; set; }
|
||||
|
||||
//public void CreateDataId(Guid Id)
|
||||
//{
|
||||
// this.Id = Id;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,22 +375,30 @@ namespace JiShe.CollectBus.Common.BuildSendDatas
|
||||
|
||||
#region 排序
|
||||
var timeSets = timeSetDetails;
|
||||
Dictionary<int, TimeSetDetail> dicTsDetails = new Dictionary<int, TimeSetDetail>();
|
||||
// 旧
|
||||
//Dictionary<int, TimeSetDetail> dicTsDetails = new Dictionary<int, TimeSetDetail>();
|
||||
//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<TimeSetDetail> orderTsDetails = new List<TimeSetDetail>();
|
||||
//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<TimeSetDetail> orderTsDetails = new List<TimeSetDetail>();
|
||||
foreach (var key in sortKeys)
|
||||
{
|
||||
orderTsDetails.Add(dicTsDetails[key]);
|
||||
}
|
||||
|
||||
timeSetDetails = orderTsDetails;
|
||||
timeSetDetails = timeSets;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@ -11,6 +11,11 @@ namespace JiShe.CollectBus.Common.BuildSendDatas
|
||||
/// </summary>
|
||||
public class TasksToBeIssueModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 上次下发任务的时间
|
||||
/// </summary>
|
||||
public DateTime? LastTaskTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下个任务时间
|
||||
/// </summary>
|
||||
|
||||
@ -98,22 +98,22 @@ namespace JiShe.CollectBus.Common.Consts
|
||||
/// <summary>
|
||||
/// AFN00H上行主题格式
|
||||
/// </summary>
|
||||
public const string SubscriberAFN00ReceivedEventNameTemp = "received.afn00h.event";
|
||||
public const string SubscriberAFN00HReceivedEventNameTemp = "received.afn00h.event";
|
||||
|
||||
/// <summary>
|
||||
/// AFN01H上行主题格式
|
||||
/// </summary>
|
||||
public const string SubscriberAFN00HReceivedEventNameTemp = "received.afn01h.event";
|
||||
public const string SubscriberAFN01HReceivedEventNameTemp = "received.afn01h.event";
|
||||
|
||||
/// <summary>
|
||||
/// AFN02H上行主题格式
|
||||
/// </summary>
|
||||
public const string SubscriberAFN01HReceivedEventNameTemp = "received.afn02h.event";
|
||||
public const string SubscriberAFN02HReceivedEventNameTemp = "received.afn02h.event";
|
||||
|
||||
/// <summary>
|
||||
/// AFN03H上行主题格式
|
||||
/// </summary>
|
||||
public const string SubscriberAFN02HReceivedEventNameTemp = "received.afn03h.event";
|
||||
public const string SubscriberAFN03HReceivedEventNameTemp = "received.afn03h.event";
|
||||
|
||||
/// <summary>
|
||||
/// AFN04H上行主题格式
|
||||
|
||||
65
shared/JiShe.CollectBus.Common/Encrypt/EncryptUtil.cs
Normal file
65
shared/JiShe.CollectBus.Common/Encrypt/EncryptUtil.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 各种加密辅助类
|
||||
/// </summary>
|
||||
public static class EncryptUtil
|
||||
{
|
||||
#region MD5加密
|
||||
|
||||
/// <summary>
|
||||
/// MD5加密
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加权MD5加密
|
||||
/// </summary>
|
||||
public static string Md5Fun(this string value, string salt)
|
||||
{
|
||||
return salt == null ? value.Md5Fun() : (value + "『" + salt + "』").Md5Fun();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// HashAlgorithm 加密统一方法
|
||||
/// </summary>
|
||||
private static string HashAlgorithmBase(HashAlgorithm hashAlgorithmObj, string source, Encoding encoding)
|
||||
{
|
||||
byte[] btStr = encoding.GetBytes(source);
|
||||
byte[] hashStr = hashAlgorithmObj.ComputeHash(btStr);
|
||||
return hashStr.Bytes2Str();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换成字符串
|
||||
/// </summary>
|
||||
private static string Bytes2Str(this IEnumerable<byte> source, string formatStr = "{0:X2}")
|
||||
{
|
||||
StringBuilder pwd = new StringBuilder();
|
||||
foreach (byte btStr in source)
|
||||
{
|
||||
pwd.AppendFormat(formatStr, btStr);
|
||||
}
|
||||
return pwd.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,4 +368,9 @@ namespace JiShe.CollectBus.Common.Enums
|
||||
HardwareReleaseDate=38
|
||||
}
|
||||
|
||||
public enum FN
|
||||
{
|
||||
登录 = 1,
|
||||
心跳 = 3
|
||||
}
|
||||
}
|
||||
|
||||
16
shared/JiShe.CollectBus.Common/Enums/TimestampUnit.cs
Normal file
16
shared/JiShe.CollectBus.Common/Enums/TimestampUnit.cs
Normal file
@ -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 // 纳秒级
|
||||
}
|
||||
}
|
||||
@ -182,24 +182,6 @@ namespace JiShe.CollectBus.Common.Extensions
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间毫秒级时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetCurrentTimeMillis()
|
||||
{
|
||||
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将Unix时间戳转换为日期时间
|
||||
/// </summary>
|
||||
/// <param name="millis"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime FromUnixMillis(long millis)
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(millis).DateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 采集时间节点计算
|
||||
@ -233,5 +215,43 @@ namespace JiShe.CollectBus.Common.Extensions
|
||||
.AddHours(hours)
|
||||
.AddMinutes(minutes);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 格式化为微秒(μs)
|
||||
/// </summary>
|
||||
/// <param name="dt"></param>
|
||||
/// <returns></returns>
|
||||
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}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化为纳秒(ns)
|
||||
/// </summary>
|
||||
/// <param name="dt"></param>
|
||||
/// <returns></returns>
|
||||
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}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 毫米、微秒、纳秒时间戳转DateTime
|
||||
/// </summary>
|
||||
/// <param name="dateLong"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间毫秒级时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetCurrentTimeMillis()
|
||||
{
|
||||
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将Unix时间戳转换为日期时间
|
||||
/// </summary>
|
||||
/// <param name="millis"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime FromUnixMillis(long millis)
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(millis).DateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 DateTime 时间转换为 DateTimeOffset 时间
|
||||
/// </summary>
|
||||
/// <param name="rawDateTime"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1128,6 +1128,21 @@ namespace JiShe.CollectBus.Common.Extensions
|
||||
return string.Join(" ", strArr.Reverse());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 高低位反转,并转换成字符串
|
||||
/// </summary>
|
||||
/// <param name="list">16进制字符集合</param>
|
||||
/// <param name="index">16进制字符集合开始索引</param>
|
||||
/// <param name="count">取多少个数据</param>
|
||||
/// <returns></returns>
|
||||
public static string ListReverseToStr(this List<string> list, int index, int count)
|
||||
{
|
||||
var addrList = list.GetRange(index, count);
|
||||
addrList.Reverse();//高低位反转
|
||||
return string.Join("", addrList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二进制转十六进制
|
||||
/// </summary>
|
||||
@ -1174,6 +1189,25 @@ namespace JiShe.CollectBus.Common.Extensions
|
||||
return binaryValue;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 十六进制转二进制
|
||||
/// 不足4位,前面补0
|
||||
/// </summary>
|
||||
/// <param name="hexString"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据值加33
|
||||
/// </summary>
|
||||
|
||||
@ -769,11 +769,11 @@ namespace JiShe.CollectBus.Common.Helpers
|
||||
/// <param name="pn"></param>
|
||||
/// <param name="msa"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
68
shared/JiShe.CollectBus.Common/Helpers/TimestampHelper.cs
Normal file
68
shared/JiShe.CollectBus.Common/Helpers/TimestampHelper.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间戳帮助类
|
||||
/// </summary>
|
||||
public static class TimestampHelper
|
||||
{
|
||||
private static readonly long UnixEpochTicks = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).Ticks;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前 DateTimeOffset 距离 Unix 纪元(1970-01-01)的微秒数
|
||||
/// </summary>
|
||||
public static long ToUnixTimeMicroseconds(this DateTimeOffset dateTimeOffset)
|
||||
{
|
||||
// Ticks 单位是 100 纳秒,转换为微秒需除以 10
|
||||
long elapsedTicks = dateTimeOffset.Ticks - UnixEpochTicks;
|
||||
return elapsedTicks / 10; // 1 微秒 = 1000 纳秒 = 10 Ticks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前 DateTimeOffset 距离 Unix 纪元(1970-01-01)的纳秒数
|
||||
/// </summary>
|
||||
public static long ToUnixTimeNanoseconds(this DateTimeOffset dateTimeOffset)
|
||||
{
|
||||
long nanoseconds = (dateTimeOffset.Ticks - UnixEpochTicks) * 100;
|
||||
return nanoseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 long 类型时间戳转换为 DateTime(UTC)
|
||||
/// </summary>
|
||||
/// <param name="timestamp">时间戳数值</param>
|
||||
/// <param name="unit">时间戳单位</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link href="libs/bootstrap/css/bootstrap.min.css" rel="stylesheet"/>
|
||||
<title>后端服务</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@ -84,7 +84,8 @@
|
||||
"SaslPassword": "lixiao1980",
|
||||
"KafkaReplicationFactor": 3,
|
||||
"NumPartitions": 30,
|
||||
"ServerTagName": "JiSheCollectBus20"
|
||||
"ServerTagName": "JiSheCollectBus99",
|
||||
"FirstCollectionTime": "2025-04-22 16:07:00"
|
||||
},
|
||||
"IoTDBOptions": {
|
||||
"UserName": "root",
|
||||
@ -92,7 +93,7 @@
|
||||
"ClusterList": [ "192.168.1.9:6667" ],
|
||||
"PoolSize": 2,
|
||||
"DataBaseName": "energy",
|
||||
"OpenDebugMode": true,
|
||||
"OpenDebugMode": false,
|
||||
"UseTableSessionPoolByDefault": false
|
||||
},
|
||||
"Cassandra": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user