From 5772ce906d9dc0afe838892ea4cd8388fdaeaa97 Mon Sep 17 00:00:00 2001 From: ChenYi <296215406@outlook.com> Date: Thu, 3 Apr 2025 15:38:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84IOTDB=20Provider=E5=B0=81?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/SampleAppService.cs | 5 +- .../AttributeInfo/NumericalOrderAttribute.cs | 28 + .../Extensions/EnumExtensions.cs | 68 ++ .../Helpers/CommonHelper.cs | 764 ++++++++++++++++++ .../Helpers/JsonHelper.cs | 28 + .../Helpers/SelectResult.cs | 35 + .../Interface/IIoTDBProvider.cs | 15 +- .../JiShe.CollectBus.IoTDBProvider.csproj | 3 + .../Options/QueryOptions.cs | 5 + .../Provider/DeviceMetadata.cs | 8 +- .../Provider/IoTDBProvider.cs | 396 ++++++--- 11 files changed, 1252 insertions(+), 103 deletions(-) create mode 100644 src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs create mode 100644 src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs create mode 100644 src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs create mode 100644 src/JiShe.CollectBus.Common/Helpers/SelectResult.cs diff --git a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs index f76ce54..9c2a86f 100644 --- a/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs +++ b/src/JiShe.CollectBus.Application/Samples/SampleAppService.cs @@ -25,15 +25,14 @@ public class SampleAppService : CollectBusAppService, ISampleAppService ElectricityMeter meter = new ElectricityMeter() { SystemName = "Energy", - DeviceId = "402440506" - , + DeviceId = "402440506", DeviceType = "Ammeter", Current = 10, MeterModel = "DDZY-1980", ProjectCode = "10059", Voltage = 10 }; - await _iotDBProvider.InsertAsync(meter); + await _iotDBProvider.InsertAsync(meter,2); } public Task GetAsync() diff --git a/src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs b/src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs new file mode 100644 index 0000000..a062f16 --- /dev/null +++ b/src/JiShe.CollectBus.Common/AttributeInfo/NumericalOrderAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.AttributeInfo +{ + /// + /// 排序序号 + /// + public class NumericalOrderAttribute : Attribute + { + /// + /// 序号 + /// + public int Index { get; set; } + + /// + /// 排序序号 + /// + /// + public NumericalOrderAttribute(int index) + { + Index = index; + } + } +} diff --git a/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs b/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..222984e --- /dev/null +++ b/src/JiShe.CollectBus.Common/Extensions/EnumExtensions.cs @@ -0,0 +1,68 @@ +using System; + +namespace JiShe.CollectBus.Common.Extensions +{ + public static class EnumExtensions + { + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => e.ToString(), + e => Convert.ToInt32(e) + ); + } + + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToEnumDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => e.ToString(), + e => e + ); + } + + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToValueNameDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => Convert.ToInt32(e), + e => e.ToString() + ); + } + + /// + /// 将枚举转换为字典 + /// + /// + /// + public static Dictionary ToEnumNameDictionary() where TEnum : Enum + { + return Enum.GetValues(typeof(TEnum)) + .Cast() + .ToDictionary( + e => e, + e => e.ToString() + ); + } + + } +} diff --git a/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs new file mode 100644 index 0000000..fa38d1d --- /dev/null +++ b/src/JiShe.CollectBus.Common/Helpers/CommonHelper.cs @@ -0,0 +1,764 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using JiShe.CollectBus.Common.AttributeInfo; + +namespace JiShe.CollectBus.Common.Helpers +{ + public static class CommonHelper + { + /// + /// 获得无符号GUID + /// + /// + public static string GetGUID() + { + return Guid.NewGuid().ToString("N"); + } + + /// + /// 获取时间戳 + /// + /// 是否返回秒,false返回毫秒 + /// + public static long GetTimeStampTen(bool isSeconds) + { + if (isSeconds) + { + return DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + } + else + { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + } + } + + /// + /// 获取指定长度的随机数 + /// + /// + /// + public static string GetRandomNumber(int length = 8) + { + if (length <= 8) + { + length = 8; + } + + if (length > 31) + { + length = 32; + } + + var randomArray = RandomNumberGenerator.GetBytes(length); + StringBuilder stringBuilder = new StringBuilder(); + foreach (var item in randomArray) + { + stringBuilder.Append(item); + } + + return stringBuilder.ToString().Substring(0, length); + } + + /// + /// C#反射遍历对象属性获取键值对 + /// + /// 对象类型 + /// 对象 + public static Dictionary GetClassProperties(T model) + { + Type t = model.GetType(); + List propertyList = new List(); + + PropertyInfo[] tempPropertyList = t.GetProperties(); + if (tempPropertyList != null && tempPropertyList.Length > 0) + { + propertyList.AddRange(tempPropertyList); + } + + var parentPropertyInfo = t.BaseType?.GetProperties(); + if (parentPropertyInfo != null && parentPropertyInfo.Length > 0) + { + foreach (var item in parentPropertyInfo) + { + if (!propertyList.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + propertyList.Add(item); + } + } + } + + Dictionary resultData = new Dictionary(); + + foreach (PropertyInfo item in propertyList) + { + resultData.Add(item.Name, item.GetValue(model, null)); + } + + return resultData; + } + + /// + /// C#反射遍历对象属性,将键值对赋值给属性 + /// + public static object SetClassProperties(string typeModel, Dictionary keyValues) + { + if (keyValues.Count <= 0) + { + return null; + } + + Type tType = Type.GetType(typeModel); + + + PropertyInfo[] PropertyList = tType.GetProperties(); + + object objModel = tType.Assembly.CreateInstance(tType.FullName); + foreach (PropertyInfo item in PropertyList) + { + if (keyValues.ContainsKey(item.Name)) + { + object objectValue = keyValues[item.Name]; + item.SetValue(objModel, objectValue); + } + } + + return objModel; + } + + /// + /// 取得某月的第一天 + /// + /// 要取得月份第一天的时间 + /// + public static DateTime FirstDayOfMonth(this DateTime datetime) + { + return datetime.AddDays(1 - datetime.Day); + } + + /// + /// 取得某月的最后一天 + /// + /// 要取得月份最后一天的时间 + /// + public static DateTime LastDayOfMonth(this DateTime datetime) + { + return datetime.AddDays(1 - datetime.Day).AddMonths(1).AddDays(-1); + } + + /// + /// 取得某月第一天0点以及最后一天的23:59:59时间范围 + /// + /// + /// + public static Tuple GetMonthDateRange(this DateTime datetime) + { + var lastDayOfMonthDate = LastDayOfMonth(datetime); + return new Tuple(datetime.FirstDayOfMonth(), new DateTime(lastDayOfMonthDate.Year, lastDayOfMonthDate.Month, lastDayOfMonthDate.Day, 23, 59, 59)); + } + + /// + /// 取得某一天0点到当月最后一天的23:59:59时间范围 + /// + /// + /// + public static Tuple GetCurrentDateToLastDayRange(this DateTime datetime) + { + var lastDayOfMonthDate = LastDayOfMonth(datetime); + return new Tuple(datetime.Date, new DateTime(lastDayOfMonthDate.Year, lastDayOfMonthDate.Month, lastDayOfMonthDate.Day, 23, 59, 59)); + } + + /// + /// 取得某一天0点到23:59:59时间范围 + /// + /// + /// + public static Tuple GetCurrentDateRange(this DateTime datetime) + { + return new Tuple(datetime.Date, new DateTime(datetime.Year, datetime.Month, datetime.Day, 23, 59, 59)); + } + + /// + /// 获取指定枚举的所有 Attribute 说明以及value组成的键值对 + /// + /// 对象类 + /// false获取字段、true获取属性 + /// + public static List GetEnumAttributeList(Type type, bool getPropertie = false) + { + if (type == null) + { + return null; + } + + List selectResults = new List(); + List memberInfos = new List(); + + if (getPropertie == false) + { + FieldInfo[] fieldArray = type.GetFields(); + if (null == fieldArray || fieldArray.Length <= 0) + { + return null; + } + + memberInfos.AddRange(fieldArray); + //获取父类的字段 + var parentFieldInfo = type.BaseType?.GetFields(); + if (parentFieldInfo != null && parentFieldInfo.Length > 0) + { + foreach (var item in parentFieldInfo) + { + if (!memberInfos.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + memberInfos.Add(item); + } + } + } + } + else + { + PropertyInfo[] properties = type.GetProperties(); + if (null == properties || properties.Length <= 0) + { + return null; + } + + memberInfos.AddRange(properties); + //获取父类的属性 + var parentPropertyInfo = type.BaseType?.GetProperties(); + if (parentPropertyInfo != null && parentPropertyInfo.Length > 0) + { + foreach (var item in parentPropertyInfo) + { + if (!memberInfos.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + memberInfos.Add(item); + } + } + } + } + + foreach (var item in memberInfos) + { + DescriptionAttribute[] EnumAttributes = + (DescriptionAttribute[])item.GetCustomAttributes(typeof(DescriptionAttribute), false); + + dynamic infoObject = null; + if (getPropertie == false) + { + infoObject = (FieldInfo)item; + } + else + { + infoObject = (PropertyInfo)item; + } + + if (EnumAttributes.Length > 0) + { + SelectResult selectResult = new SelectResult() + { + Key = Convert.ToInt32(infoObject.GetValue(null)).ToString(), + Value = EnumAttributes[0].Description, + }; + + selectResults.Add(selectResult); + } + + DisplayAttribute[] DisplayAttributes = + (DisplayAttribute[])item.GetCustomAttributes(typeof(DisplayAttribute), false); + if (DisplayAttributes.Length > 0) + { + SelectResult selectResult = + selectResults.FirstOrDefault(e => e.Key == Convert.ToInt32(infoObject.GetValue(null)).ToString()); + if (selectResult != null) + { + selectResult.SecondValue = DisplayAttributes[0].Name; + } + } + } + + return selectResults; + } + + /// + /// 获取指定枚举的所有 Attribute 说明以及value组成的键值对 + /// + /// 对象类 + /// 第三个标签类型 + /// 第三个标签类型取值名称 + /// false获取字段、true获取属性 + /// + public static List GetEnumAttributeListWithThirdValue(Type type, Type thirdAttributeType, string thirdAttributePropertieName, bool getPropertie = false) + { + if (type == null) + { + return null; + } + + List selectResults = new List(); + List memberInfos = new List(); + + if (getPropertie == false) + { + FieldInfo[] EnumInfo = type.GetFields(); + if (null == EnumInfo || EnumInfo.Length <= 0) + { + return null; + } + memberInfos.AddRange(EnumInfo); + var parentFieldInfo = type.BaseType?.GetFields(); + if (parentFieldInfo != null && parentFieldInfo.Length > 0) + { + memberInfos.AddRange(parentFieldInfo); + } + } + else + { + PropertyInfo[] EnumInfo = type.GetProperties(); + if (null == EnumInfo || EnumInfo.Length <= 0) + { + return null; + } + memberInfos.AddRange(EnumInfo); + var parentPropertyInfo = type.BaseType?.GetProperties(); + if (parentPropertyInfo != null && parentPropertyInfo.Length > 0) + { + memberInfos.AddRange(parentPropertyInfo); + } + } + + foreach (var item in memberInfos) + { + var thirdAttributes = item. + GetCustomAttributes(thirdAttributeType, false); + if (thirdAttributes == null || thirdAttributes.Length <= 0) + { + continue; + } + + DescriptionAttribute[] descriptionAttributes = (DescriptionAttribute[])item. + GetCustomAttributes(typeof(DescriptionAttribute), false); + + dynamic infoObject = null; + if (getPropertie == false) + { + infoObject = (FieldInfo)item; + } + else + { + infoObject = (PropertyInfo)item; + } + + if (descriptionAttributes.Length > 0) + { + SelectResult selectResult = new SelectResult() + { + Key = infoObject.Name, + Value = descriptionAttributes[0].Description, + }; + + selectResults.Add(selectResult); + } + + DisplayAttribute[] displayAttributes = (DisplayAttribute[])item. + GetCustomAttributes(typeof(DisplayAttribute), false); + if (displayAttributes.Length > 0) + { + SelectResult selectResult = selectResults.FirstOrDefault(e => e.Key == infoObject.Name); + if (selectResult != null) + { + selectResult.SecondValue = displayAttributes[0].Name; + } + } + + if (thirdAttributes.Length > 0 && !string.IsNullOrWhiteSpace(thirdAttributePropertieName)) + { + foreach (var attr in thirdAttributes) + { + // 使用反射获取特性的属性值 + var properties = thirdAttributeType.GetProperties(); + foreach (var prop in properties) + { + // 假设你要获取特性的某个属性值,例如 TypeName + if (prop.Name == thirdAttributePropertieName) + { + object value = prop.GetValue(attr); + SelectResult selectResult = selectResults.FirstOrDefault(e => e.Key == infoObject.Name); + if (selectResult != null) + { + selectResult.ThirdValue = value?.ToString(); // 将属性值赋给 ThirdValue + } + break; // 如果找到了需要的属性,可以跳出循环 + } + } + } + } + } + + return selectResults; + } + + /// + /// 获取指定类、指定常量值的Description说明 + /// 包含直接继承的父级字段 + /// + /// 对象类 + /// + /// + public static List> GetTypeDescriptionListToTuple(Type t, bool getPropertie = false) + { + if (t == null) + { + return null; + } + + List memberInfos = new List(); + + if (getPropertie == false) + { + FieldInfo[] fieldInfo = t.GetFields(); + if (null == fieldInfo || fieldInfo.Length <= 0) + { + return null; + } + + memberInfos.AddRange(fieldInfo); + var parentFieldInfo = t.BaseType?.GetFields(); + if (parentFieldInfo != null && parentFieldInfo.Length > 0) + { + foreach (var item in parentFieldInfo) + { + if (!memberInfos.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + memberInfos.Add(item); + } + } + } + } + else + { + PropertyInfo[] fieldInfo = t.GetProperties(); + if (null == fieldInfo || fieldInfo.Length <= 0) + { + return null; + } + + memberInfos.AddRange(fieldInfo); + var parentPropertyInfo = t.BaseType?.GetProperties(); + if (parentPropertyInfo != null && parentPropertyInfo.Length > 0) + { + foreach (var item in parentPropertyInfo) + { + if (!memberInfos.Any(d => d.Name == item.Name)) //如果子类已经包含父类的属性或者字段,跳过不处理 + { + memberInfos.Add(item); + } + } + } + } + + List> tuples = new List>(); + + foreach (var item in memberInfos) + { + DescriptionAttribute[] descriptionAttribute = + (DescriptionAttribute[])item.GetCustomAttributes(typeof(DescriptionAttribute), false); + + NumericalOrderAttribute[] indexAttributes = + (NumericalOrderAttribute[])item.GetCustomAttributes(typeof(NumericalOrderAttribute), false); + + + if (descriptionAttribute.Length > 0 && indexAttributes.Length > 0) + { + Tuple tuple = new Tuple(item.Name, + descriptionAttribute[0].Description, indexAttributes[0].Index); + tuples.Add(tuple); + } + } + + return tuples; + } + + /// + /// 获取指定类、指定常量值的常量说明 + /// + /// 常量字段名称 + ///属性还是字段 + /// + public static string GetTypeDescriptionName(string fieldName, bool getPropertie = false) + { + if (string.IsNullOrEmpty(fieldName)) + { + return ""; + } + + MemberInfo memberInfo = null; + if (getPropertie == false) + { + memberInfo = typeof(T).GetField(fieldName); + } + else + { + memberInfo = typeof(T).GetProperty(fieldName); + } + + if (null == memberInfo) + { + return ""; + } + + DescriptionAttribute[] EnumAttributes = + (DescriptionAttribute[])memberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (EnumAttributes.Length <= 0) + { + return ""; + } + + return EnumAttributes[0].Description; + } + + /// + /// 获取指定命名空间下指定常量值的常量说明 + /// + /// 常量字段名称 + /// 命名空间,主要用来找到对应程序集 + ///属性还是字段 + /// + public static string GetTypeDescriptionName(string fieldName, string assemblyName, bool getPropertie = false) + { + if (string.IsNullOrWhiteSpace(fieldName) || string.IsNullOrWhiteSpace(assemblyName)) + { + return null; + } + + string desc = ""; + foreach (var item in GetEnumList(assemblyName)) + { + desc = GetTypeDescriptionName(item, fieldName, getPropertie); + if (!string.IsNullOrEmpty(desc)) + { + break; + } + } + + return desc; + } + + /// + /// 获取指定类、指定常量值的常量说明 + /// + /// 对象类 + /// 常量字段名称 + ///属性还是字段 + /// + public static string GetTypeDescriptionName(this Type t, string fieldName, bool getPropertie = false) + { + if (string.IsNullOrWhiteSpace(fieldName)) + { + return ""; + } + + MemberInfo memberInfo = null; + if (getPropertie == false) + { + memberInfo = t.GetField(fieldName); + } + else + { + memberInfo = t.GetProperty(fieldName); + } + + if (null != memberInfo) + { + DescriptionAttribute[] EnumAttributes = + (DescriptionAttribute[])memberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (EnumAttributes.Length > 0) + { + return EnumAttributes[0].Description; + } + } + + return ""; + } + + /// + /// 扩展方法,获得枚举值集合 + /// + ///枚举的DisplayName + public static List GetEnumList() where T : Enum + { + List enumList = new List(); + foreach (T value in Enum.GetValues(typeof(T))) + { + enumList.Add(value); + } + + return enumList; + } + + private static List GetEnumList(string assemblyName) + { + if (!String.IsNullOrEmpty(assemblyName)) + { + Assembly assembly = Assembly.Load(assemblyName); + List ts = assembly.GetTypes().Where(x => x.GetTypeInfo().IsClass).ToList(); + return ts; + } + + return new List(); + } + + /// + /// 扩展方法,获得枚举的Display值 + /// + ///枚举值 + ///当枚举值没有定义DisplayNameAttribute,是否使用枚举名代替,默认是使用 + ///属性还是字段 + ///枚举的DisplayName + public static string GetDisplayName(this Enum value, Boolean nameInstead = true, bool getPropertie = false) + { + Type type = value.GetType(); + string name = Enum.GetName(type, value); + if (name == null) + { + return null; + } + + DisplayAttribute attribute = null; + + if (getPropertie == false) + { + attribute = Attribute.GetCustomAttribute(type.GetField(name), typeof(DisplayAttribute)) as DisplayAttribute; + } + else + { + attribute = + Attribute.GetCustomAttribute(type.GetProperty(name), typeof(DisplayAttribute)) as DisplayAttribute; + } + + + if (attribute == null && nameInstead == true) + { + return name; + } + + return attribute == null ? null : attribute.Name; + } + + /// + /// 获取枚举的描述信息 + /// + /// + /// + public static string GetEnumDescription(this Enum value) + { + var name = value.ToString(); + var field = value.GetType().GetField(name); + if (field == null) return name; + + var att = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute), false); + + return att == null ? field.Name : ((DescriptionAttribute)att).Description; + } + + + + /// + /// 将传入的字符串中间部分字符替换成特殊字符 + /// + /// 需要替换的字符串 + /// 前保留长度 + /// 尾保留长度 + /// 特殊字符 + /// 被特殊字符替换的字符串 + public static string ReplaceWithSpecialChar(this string value, int startLen = 1, int endLen = 1, + char specialChar = '*') + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + try + { + + if (value.Length <= startLen + endLen) + { + var temStartVal = value.Substring(0, startLen); + return $"{temStartVal}{"".PadLeft(endLen, specialChar)}"; + } + + if (value.Length == 10 && endLen == 1) + { + endLen = 3; + } + + var startVal = value.Substring(0, startLen); + var endVal = value.Substring(value.Length - endLen); + if (value.Length == 2) + { + endVal = ""; + } + + value = $"{startVal}{endVal.PadLeft(value.Length - startLen, specialChar)}"; + } + catch (Exception) + { + throw; + } + + return value; + } + + /// + /// Linux下字体名称转换 + /// + /// + /// + public static string GetLinuxFontFamily(this string fontValue) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (fontValue == "楷体") + { + fontValue = "KaiTi"; + } + else if (fontValue == "隶书") + { + fontValue = "LiSu"; + } + else if (fontValue == "宋体") + { + fontValue = "SimSun"; + } + else if (fontValue == "微软雅黑") + { + fontValue = "Microsoft YaHei"; + } + else if (fontValue == "新宋体") + { + fontValue = "NSimSun"; + } + else if (fontValue == "仿宋") + { + fontValue = "FangSong"; + } + else if (fontValue == "黑体") + { + fontValue = "SimHei"; + } + + } + + return fontValue; + } + } +} diff --git a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs b/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs index 029ae1f..bdd46f6 100644 --- a/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs +++ b/src/JiShe.CollectBus.Common/Helpers/JsonHelper.cs @@ -123,4 +123,32 @@ namespace JiShe.CollectBus.Common.Helpers writer.WriteStringValue(value.ToString(_dateFormatString)); } } + + /// + /// Unix格式时间格式化 + /// + public class UnixTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + if (long.TryParse(reader.GetString(), out long timestamp)) + return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime; + } + + if (reader.TokenType == JsonTokenType.Number) + { + long timestamp = reader.GetInt64(); + return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime; + } + return reader.GetDateTime(); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + long timestamp = new DateTimeOffset(value).ToUnixTimeSeconds(); + writer.WriteStringValue(timestamp.ToString()); + } + } } diff --git a/src/JiShe.CollectBus.Common/Helpers/SelectResult.cs b/src/JiShe.CollectBus.Common/Helpers/SelectResult.cs new file mode 100644 index 0000000..4ccaf99 --- /dev/null +++ b/src/JiShe.CollectBus.Common/Helpers/SelectResult.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace JiShe.CollectBus.Common.Helpers +{ + /// + /// 下拉框选项元素 + /// + public class SelectResult + { + /// + /// 下拉框 键 + /// + public string Key { get; set; } + + /// + /// 下拉框 值 + /// + public string Value { get; set; } + + /// + /// 下拉框 值2 + /// + public string SecondValue { get; set; } + + /// + /// 下拉框 值3 + /// + public object ThirdValue { get; set; } + + } +} diff --git a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs index 026a95a..71cc741 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Interface/IIoTDBProvider.cs @@ -16,16 +16,27 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// /// + /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 /// - Task InsertAsync(T entity) where T : IoTEntity; + Task InsertAsync(T entity, int buildTabletMode) where T : IoTEntity; /// /// 批量插入数据 /// /// /// + /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 /// - Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity; + Task BatchInsertAsync(IEnumerable entities, int buildTabletMode) where T : IoTEntity; + + + /// + /// 删除数据 + /// + /// + /// + /// + Task DeleteAsync(QueryOptions options) where T : IoTEntity; /// /// 查询数据 diff --git a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj index 717b0f6..137cee3 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj +++ b/src/JiShe.CollectBus.IoTDBProvider/JiShe.CollectBus.IoTDBProvider.csproj @@ -10,4 +10,7 @@ + + + diff --git a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs index 019bd28..d04cd48 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Options/QueryOptions.cs @@ -11,6 +11,11 @@ namespace JiShe.CollectBus.IoTDBProvider /// public class QueryOptions { + /// + /// 表名或标签名 + /// + public required string TableNameOrTagName { get; set; } + /// /// 分页 /// diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs index 8e84776..0d5b408 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/DeviceMetadata.cs @@ -13,17 +13,17 @@ namespace JiShe.CollectBus.IoTDBProvider public class DeviceMetadata { /// - /// 测量值集合,用于构建Table的测量值,也就是field参数 + /// 测量值集合,用于构建Table的测量值,也就是columnNames参数 /// - public List Measurements { get; } = new(); + public List ColumnNames { get; } = new(); /// - /// 列类型集合,用于构建Table的列类型,也就是columnCategory参数 + /// 列类型集合,用于构建Table的列类型,也就是columnCategories参数 /// public List ColumnCategories { get; } = new(); /// - /// 值类型集合,用于构建Table的值类型,也就是dataType参数 + /// 值类型集合,用于构建Table的值类型,也就是dataTypes参数 /// public ListDataTypes { get; } = new(); } diff --git a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs index 449884a..39e07c0 100644 --- a/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs +++ b/src/JiShe.CollectBus.IoTDBProvider/Provider/IoTDBProvider.cs @@ -1,5 +1,8 @@ using Apache.IoTDB; using Apache.IoTDB.DataStructure; +using JiShe.CollectBus.Common.Extensions; +using JiShe.CollectBus.Common.Helpers; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Collections.Concurrent; @@ -19,8 +22,9 @@ namespace JiShe.CollectBus.IoTDBProvider private readonly IoTDBOptions _options; private readonly TableSessionPool _sessionPool; private static readonly ConcurrentDictionary _metadataCache = new(); + private readonly ILogger _logger; - public IoTDBProvider(IOptions options) + public IoTDBProvider(IOptions options, ILogger logger) { _options = options.Value; @@ -32,58 +36,20 @@ namespace JiShe.CollectBus.IoTDBProvider .Build(); _sessionPool.Open(false).Wait(); + _logger = logger; } - /// - /// 获取设备元数据 - /// - /// - /// - private DeviceMetadata GetMetadata() where T : IoTEntity - { - return _metadataCache.GetOrAdd(typeof(T), type => - { - var metadata = new DeviceMetadata(); - foreach (var prop in type.GetProperties()) - { - //标签列 - var attrTAG = prop.GetCustomAttribute(); - if (attrTAG != null) - { - metadata.ColumnCategories.Add(ColumnCategory.TAG); - } - - //属性列 - var attrATTRIBUTE = prop.GetCustomAttribute(); - if (attrATTRIBUTE != null) - { - metadata.ColumnCategories.Add(ColumnCategory.ATTRIBUTE); - } - - //数据列 - var attrFIELD = prop.GetCustomAttribute(); - if (attrFIELD != null) - { - metadata.ColumnCategories.Add(ColumnCategory.FIELD); - metadata.Measurements.Add(prop.Name); - metadata.DataTypes.Add(GetDataTypeFromStr(prop.PropertyType.Name)); - } - } - return metadata; - }); - } - /// /// 插入数据 /// /// /// /// - public async Task InsertAsync(T entity) where T : IoTEntity + public async Task InsertAsync(T entity, int buildTabletMode) where T : IoTEntity { var metadata = GetMetadata(); - var tablet = BuildTablet(new[] { entity }, metadata); + var tablet = BuildTablet(new[] { entity }, metadata, buildTabletMode); await _sessionPool.InsertAsync(tablet); } @@ -93,58 +59,46 @@ namespace JiShe.CollectBus.IoTDBProvider /// /// /// - public async Task BatchInsertAsync(IEnumerable entities) where T : IoTEntity + public async Task BatchInsertAsync(IEnumerable entities, int buildTabletMode) where T : IoTEntity { - var metadata = GetMetadata(); + var metadata = GetMetadata(); var batchSize = 1000; var batches = entities.Chunk(batchSize); foreach (var batch in batches) { - var tablet = BuildTablet(batch, metadata); + var tablet = BuildTablet(batch, metadata, buildTabletMode); await _sessionPool.InsertAsync(tablet); } } + /// - /// 构建表模型 + /// 删除数据 /// /// - /// - /// + /// /// - private Tablet BuildTablet(IEnumerable entities, DeviceMetadata metadata) where T : IoTEntity + public async Task DeleteAsync(QueryOptions options) where T : IoTEntity { - var deviceId = DevicePathBuilder.GetDeviceId(entities.First()); - var timestamps = new List(); - var values = new List>(); + var query = BuildDeleteSQL(options); + var sessionDataSet = await _sessionPool.ExecuteQueryStatementAsync(query); - foreach (var entity in entities) + if (!sessionDataSet.HasNext()) { - timestamps.Add(entity.Timestamps); - var rowValues = new List(); - foreach (var measurement in metadata.Measurements) - { - var value = typeof(T).GetProperty(measurement)?.GetValue(entity); - if(value == null) - { - throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,属性{measurement}值为空,不符合IoTDB设计标准,请赋值以后重新处理。"); - } - rowValues.Add(value); - } - values.Add(rowValues); + _logger.LogWarning($"{typeof(T).Name} 删除数据时,没有返回受影响记录数量。"); + return 0; } - return new Tablet( - deviceId, - metadata.Measurements, - metadata.DataTypes, - values, - timestamps - ); + //获取唯一结果行 + var row = sessionDataSet.Next(); + return row.Values[0]; } + + + /// /// 查询数据 /// @@ -153,7 +107,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// public async Task> QueryAsync(QueryOptions options) where T : IoTEntity, new() { - var query = BuildQuery(options); + var query = BuildQuerySQL(options); var sessionDataSet = await _sessionPool.ExecuteQueryStatementAsync(query); var result = new PagedResult @@ -165,18 +119,75 @@ namespace JiShe.CollectBus.IoTDBProvider return result; } + /// + /// 构建表模型 + /// + /// + /// 表实体 + /// 设备元数据 + /// 构建表模型方式,1 根据实体《T》直接显示指定Tag,2根据实体《T》的名称指定表名 + /// + private Tablet BuildTablet(IEnumerable entities, DeviceMetadata metadata, int buildTabletMode) where T : IoTEntity + { + var timestamps = new List(); + var values = new List>(); + + foreach (var entity in entities) + { + timestamps.Add(entity.Timestamps); + var rowValues = new List(); + foreach (var measurement in metadata.ColumnNames) + { + var value = typeof(T).GetProperty(measurement)?.GetValue(entity); + if (value == null) + { + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,属性{measurement}值为空,不符合IoTDB设计标准,请赋值以后重新处理。"); + } + rowValues.Add(value); + } + values.Add(rowValues); + } + + if (buildTabletMode == 1) + { + return new Tablet( + DevicePathBuilder.GetDeviceId(entities.First()), + metadata.ColumnNames, + metadata.ColumnCategories, + metadata.DataTypes, + values, + timestamps + ); + } + else if (buildTabletMode == 2) + { + return new Tablet( + DevicePathBuilder.GetTableName(), + metadata.ColumnNames, + metadata.ColumnCategories, + metadata.DataTypes, + values, + timestamps + ); + } + else + { + throw new Exception($"{nameof(BuildTablet)} 构建表模型{typeof(T).Name}时,buildTabletMode参数值不正确,请赋值以后重新处理。"); + } + } + /// /// 构建查询语句 /// /// /// /// - private string BuildQuery(QueryOptions options) where T : IoTEntity + private string BuildQuerySQL(QueryOptions options) where T : IoTEntity { var metadata = GetMetadata(); var sb = new StringBuilder("SELECT "); - sb.AppendJoin(", ", metadata.Measurements); - sb.Append($" FROM {DevicePathBuilder.GetTableName()}"); + sb.AppendJoin(", ", metadata.ColumnNames); + sb.Append($" FROM {options.TableNameOrTagName}"); if (options.Conditions.Any()) { @@ -188,6 +199,30 @@ namespace JiShe.CollectBus.IoTDBProvider return sb.ToString(); } + /// + /// 构建删除语句 + /// + /// + /// + /// + private string BuildDeleteSQL(QueryOptions options) where T : IoTEntity + { + var metadata = GetMetadata(); + var sb = new StringBuilder("DELETE "); + + sb.Append($" FROM {options.TableNameOrTagName}"); + + sb.AppendJoin(", ", metadata.ColumnNames); + + if (options.Conditions.Any()) + { + sb.Append(" WHERE "); + sb.AppendJoin(" AND ", options.Conditions.Select(TranslateCondition)); + } + + return sb.ToString(); + } + /// /// 将查询条件转换为SQL语句 /// @@ -213,7 +248,7 @@ namespace JiShe.CollectBus.IoTDBProvider /// private async Task GetTotalCount(QueryOptions options) where T : IoTEntity { - var countQuery = $"SELECT COUNT(*) FROM {DevicePathBuilder.GetTableName()}"; + var countQuery = $"SELECT COUNT(*) FROM {options.TableNameOrTagName}"; if (options.Conditions.Any()) { countQuery += " WHERE " + string.Join(" AND ", options.Conditions.Select(TranslateCondition)); @@ -246,7 +281,7 @@ namespace JiShe.CollectBus.IoTDBProvider }; - foreach (var measurement in metadata.Measurements) + foreach (var measurement in metadata.ColumnNames) { var value = record.Values; @@ -263,7 +298,7 @@ namespace JiShe.CollectBus.IoTDBProvider } return results; } - + /// /// 释放资源 /// @@ -272,23 +307,196 @@ namespace JiShe.CollectBus.IoTDBProvider _sessionPool?.Close().Wait(); } - private TSDataType GetDataTypeFromStr(string str) + /// + /// 获取设备元数据 + /// + /// + /// + private DeviceMetadata GetMetadata() where T : IoTEntity { - return str switch + return _metadataCache.GetOrAdd(typeof(T), type => { - "BOOLEAN" => TSDataType.BOOLEAN, - "INT32" => TSDataType.INT32, - "INT64" => TSDataType.INT64, - "FLOAT" => TSDataType.FLOAT, - "DOUBLE" => TSDataType.DOUBLE, - "TEXT" => TSDataType.TEXT, - "NULLTYPE" => TSDataType.NONE, - "TIMESTAMP" => TSDataType.TIMESTAMP, - "DATE" => TSDataType.DATE, - "BLOB" => TSDataType.BLOB, - "STRING" => TSDataType.STRING, - _ => TSDataType.STRING - }; + var columns = CollectColumnMetadata(type); + var metadata = BuildDeviceMetadata(columns); + return metadata; + }); + + //return _metadataCache.GetOrAdd(typeof(T), type => + //{ + // var metadata = new DeviceMetadata(); + // List> columns = new(); + // foreach (var prop in type.GetProperties()) + // { + // //标签列 + // var attrTAG = prop.GetCustomAttribute(); + // if (attrTAG != null) + // { + // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.TAG, GetDataTypeFromStr(prop.PropertyType.Name))); + // continue; + // } + + // //属性列 + // var attrATTRIBUTE = prop.GetCustomAttribute(); + // if (attrATTRIBUTE != null) + // { + // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.ATTRIBUTE, GetDataTypeFromStr(prop.PropertyType.Name))); + // continue; + // } + + // //数据列 + // var attrFIELD = prop.GetCustomAttribute(); + // if (attrFIELD != null) + // { + // columns.Add(Tuple.Create(prop.PropertyType.Name, ColumnCategory.FIELD, GetDataTypeFromStr(prop.PropertyType.Name))); + // } + // } + // var columnCategories = EnumExtensions.ToEnumDictionary(); + // foreach (var item in columnCategories) + // { + // if (item.Value == ColumnCategory.TAG) + // { + // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item1).ToList()); + // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.TAG).Select(d => d.Item2).ToList()); + // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.TAG).Select(d => d.Item3).ToList()); + // } + // else if (item.Value == ColumnCategory.ATTRIBUTE) + // { + // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item1).ToList()); + // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item2).ToList()); + // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.ATTRIBUTE).Select(d => d.Item3).ToList()); + // } + // else if (item.Value == ColumnCategory.FIELD) + // { + // metadata.ColumnNames.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item1).ToList()); + // metadata.ColumnCategories.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item2).ToList()); + // metadata.DataTypes.AddRange(columns.Where(d => d.Item2 == ColumnCategory.FIELD).Select(d => d.Item3).ToList()); + // } + // } + + // return metadata; + //}); } + + /// + /// 获取设备元数据的列 + /// + /// + /// + private List CollectColumnMetadata(Type type) + { + var columns = new List(); + + foreach (var prop in type.GetProperties()) + { + //按优先级顺序检查属性,避免重复反射 + ColumnInfo? column = prop.GetCustomAttribute() is not null ? new ColumnInfo( + name: prop.Name, //使用属性名 + category: ColumnCategory.TAG, + dataType: GetDataTypeFromTypeName(prop.PropertyType.Name) + ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( + prop.Name, + ColumnCategory.ATTRIBUTE, + GetDataTypeFromTypeName(prop.PropertyType.Name) + ) : prop.GetCustomAttribute() is not null ? new ColumnInfo( + prop.Name, + ColumnCategory.FIELD, + GetDataTypeFromTypeName(prop.PropertyType.Name) + ) : null; + + if (column.HasValue) + { + columns.Add(column.Value); + } + } + return columns; + } + + /// + /// 构建设备元数据 + /// + /// + /// + private DeviceMetadata BuildDeviceMetadata(List columns) + { + var metadata = new DeviceMetadata(); + + //按业务逻辑顺序处理(TAG -> FIELD -> ATTRIBUTE) + var groupedColumns = columns + .GroupBy(c => c.Category) + .ToDictionary(g => g.Key, g => g.ToList()); + + ProcessCategory(groupedColumns, ColumnCategory.TAG, metadata); + ProcessCategory(groupedColumns, ColumnCategory.ATTRIBUTE, metadata); + ProcessCategory(groupedColumns, ColumnCategory.FIELD, metadata); + + return metadata; + } + + /// + /// 处理不同列类型的逻辑 + /// + /// + /// + /// + private void ProcessCategory(IReadOnlyDictionary> groupedColumns, ColumnCategory category, DeviceMetadata metadata) + { + if (groupedColumns.TryGetValue(category, out var cols)) + { + metadata.ColumnNames.AddRange(cols.Select(c => c.Name)); + metadata.ColumnCategories.AddRange(cols.Select(c => c.Category)); + metadata.DataTypes.AddRange(cols.Select(c => c.DataType)); + } + } + + /// + /// 数据列结构 + /// + private readonly struct ColumnInfo + { + public string Name { get; } + public ColumnCategory Category { get; } + public TSDataType DataType { get; } + + public ColumnInfo(string name, ColumnCategory category, TSDataType dataType) + { + Name = name; + Category = category; + DataType = dataType; + } + } + + /// + /// 根据类型名称获取对应的 IoTDB 数据类型 + /// + /// 类型名称(不区分大小写) + /// 对应的 TSDataType,默认返回 TSDataType.STRING + private TSDataType GetDataTypeFromTypeName(string typeName) + { + if (string.IsNullOrWhiteSpace(typeName)) + return TSDataType.STRING; + + return DataTypeMap.TryGetValue(typeName.Trim(), out var dataType) + ? dataType + : TSDataType.STRING; + } + + /// + /// 根据类型名称获取 IoTDB 数据类型 + /// + private readonly IReadOnlyDictionary DataTypeMap = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["BOOLEAN"] = TSDataType.BOOLEAN, + ["INT32"] = TSDataType.INT32, + ["INT64"] = TSDataType.INT64, + ["FLOAT"] = TSDataType.FLOAT, + ["DOUBLE"] = TSDataType.DOUBLE, + ["TEXT"] = TSDataType.TEXT, + ["NULLTYPE"] = TSDataType.NONE, + ["TIMESTAMP"] = TSDataType.TIMESTAMP, + ["DATE"] = TSDataType.DATE, + ["BLOB"] = TSDataType.BLOB, + ["STRING"] = TSDataType.STRING + }; } }