优化导入导出,支持属性验证

This commit is contained in:
Kimdiego2098
2024-04-16 14:02:41 +08:00
parent d9408523a5
commit 68b02fe950
7 changed files with 131 additions and 103 deletions

View File

@@ -1,5 +1,4 @@

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
@@ -9,9 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Mvc;
@@ -96,7 +92,6 @@ public class ImportExportService : IImportExportService
{
await MiniExcel.SaveAsAsync(fs, input, configuration: config);
}
var result = _fileService.GetFileStreamResult(filePath, fileName);
return result;
}

View File

@@ -1,5 +1,4 @@

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
@@ -9,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Dynamic; // 引入 System.Dynamic 命名空间
using System.Reflection; // 引入 System.Reflection 命名空间
@@ -30,12 +27,9 @@ namespace ThingsGateway.Gateway.Application.Extensions
/// <param name="type">要转换的目标实体类型</param>
/// <param name="filter">是否过滤属性上的 IgnoreExcelAttribute 特性</param>
/// <returns>转换后的实体对象</returns>
public static object ConvertToEntity(this ExpandoObject expandoObject, Type type, bool filter)
public static object ConvertToEntity(this ExpandoObject expandoObject, Type type, Dictionary<string, PropertyInfo> properties)
{
var entity = Activator.CreateInstance(type);
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var properties = type.GetRuntimeProperties().Where(a => (!filter || a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name));
// 遍历动态对象的属性
expandoObject.ForEach(keyValuePair =>
@@ -58,13 +52,9 @@ namespace ThingsGateway.Gateway.Application.Extensions
/// <param name="expandoObject">动态对象</param>
/// <param name="filter">是否过滤属性上的 IgnoreExcelAttribute 特性</param>
/// <returns>转换后的实体对象</returns>
public static T ConvertToEntity<T>(this ExpandoObject expandoObject, bool filter) where T : new()
public static T ConvertToEntity<T>(this ExpandoObject expandoObject, Dictionary<string, PropertyInfo> properties) where T : new()
{
var entity = new T(); // 创建目标类型的实例
var type = typeof(T); // 获取目标类型的 Type 对象
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var properties = type.GetRuntimeProperties().Where(a => (!filter || a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name));
// 遍历动态对象的属性
expandoObject.ForEach(keyValuePair =>

View File

@@ -1,5 +1,4 @@

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
@@ -9,9 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Mapster;
@@ -359,12 +355,16 @@ public class ChannelService : BaseService<Channel>, IChannelService
ImportPreviews.Add(sheetName, importPreviewOutput);
channelImportPreview = importPreviewOutput;
List<Channel> channels = new();
var type = typeof(Channel);
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var channelProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name));
rows.ForEach(item =>
{
try
{
var channel = ((ExpandoObject)item!).ConvertToEntity<Channel>(true);
var channel = ((ExpandoObject)item!).ConvertToEntity<Channel>(channelProperties);
if (channel == null)
{
importPreviewOutput.HasError = true;

View File

@@ -1,5 +1,4 @@

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
@@ -9,9 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Mapster;
@@ -23,6 +19,7 @@ using MiniExcelLibs;
using SqlSugar;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Dynamic;
using System.Reflection;
@@ -376,6 +373,7 @@ public class DeviceService : BaseService<Device>, IDeviceService
List<Dictionary<string, object>> deviceExports = new();
//设备附加属性转成Dict<表名,List<Dict<列名,列数据>>>的形式
Dictionary<string, List<Dictionary<string, object>>> devicePropertys = new();
ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict = new();
#region
@@ -418,20 +416,28 @@ public class DeviceService : BaseService<Device>, IDeviceService
Dictionary<string, object> driverInfo = new();
var propDict = device.DevicePropertys;
var driverProperties = _pluginService.GetDriver(device.PluginName).DriverProperties;
var driverPropertyType = driverProperties.GetType();
var propertys = driverPropertyType.GetRuntimeProperties()
if (propertysDict.TryGetValue(device.PluginName, out var propertys))
{
}
else
{
var driverProperties = _pluginService.GetDriver(device.PluginName).DriverProperties;
propertys.Item1 = driverProperties;
var driverPropertyType = driverProperties.GetType();
propertys.Item2 = driverPropertyType.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => driverPropertyType.GetPropertyDisplayName(a.Name), a => a);
if (propertys.Any())
propertysDict.TryAdd(device.PluginName, propertys);
}
if (propertys.Item2.Any())
{
//没有包含设备名称,手动插入
driverInfo.Add(ExportString.DeviceName, device.Name);
}
//根据插件的配置属性项生成列,从数据库中获取值或者获取属性默认值
foreach (var item in propertys)
foreach (var item in propertys.Item2)
{
if (propDict.TryGetValue(item.Value.Name, out var dependencyProperty))
{
@@ -440,7 +446,7 @@ public class DeviceService : BaseService<Device>, IDeviceService
else
{
//添加对应属性数据
driverInfo.Add(item.Key, ThingsGatewayStringConverter.Default.Serialize(null, item.Value.GetValue(driverProperties)));
driverInfo.Add(item.Key, ThingsGatewayStringConverter.Default.Serialize(null, item.Value.GetValue(propertys.Item1)));
}
}
@@ -541,6 +547,7 @@ public class DeviceService : BaseService<Device>, IDeviceService
// 获取所有驱动程序,并将驱动程序名称作为键构建字典
var driverPluginNameDict = _pluginService.GetList().ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
foreach (var sheetName in sheetNames)
{
var rows = MiniExcel.Query(path, useHeaderRow: true, sheetName: sheetName).Cast<IDictionary<string, object>>();
@@ -561,6 +568,10 @@ public class DeviceService : BaseService<Device>, IDeviceService
// 创建设备列表
List<Device> devices = new();
var type = typeof(Device);
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var deviceProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name));
// 遍历每一行数据
rows.ForEach(item =>
@@ -568,7 +579,7 @@ public class DeviceService : BaseService<Device>, IDeviceService
try
{
// 尝试将导入的项转换为 Device 对象
var device = (item as ExpandoObject)?.ConvertToEntity<Device>(true);
var device = (item as ExpandoObject)?.ConvertToEntity<Device>(deviceProperties);
// 如果转换失败,则添加错误信息到导入预览结果并返回
if (device == null)
@@ -708,14 +719,28 @@ public class DeviceService : BaseService<Device>, IDeviceService
continue;
}
// 获取驱动插件实例
var driver = _pluginService.GetDriver(driverPluginType.FullName);
var type = driver.DriverProperties.GetType();
if (propertysDict.TryGetValue(driverPluginType.FullName, out var propertys))
{
}
else
{
// 获取驱动插件实例
var driver = _pluginService.GetDriver(driverPluginType.FullName);
var type = driver.DriverProperties.GetType();
// 获取动态属性字典
var propertys = type.GetProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name));
propertys.Item1 = type;
propertys.Item2 = type.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name));
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var properties = propertys.Item1.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => propertys.Item1.GetPropertyDisplayName(a.Name));
propertys.Item3 = properties;
propertysDict.TryAdd(driverPluginType.FullName, propertys);
}
// 遍历每一行数据
foreach (var item in rows)
@@ -743,7 +768,7 @@ public class DeviceService : BaseService<Device>, IDeviceService
}
// 尝试将导入的项转换为对象
var pluginProp = (item as ExpandoObject)?.ConvertToEntity(type, true);
var pluginProp = (item as ExpandoObject)?.ConvertToEntity(propertys.Item1, propertys.Item3);
// 如果转换失败,则添加错误信息到导入预览结果并返回
if (pluginProp == null)
@@ -780,7 +805,7 @@ public class DeviceService : BaseService<Device>, IDeviceService
Dictionary<string, string> devices = new();
foreach (var keyValuePair in item)
{
if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo))
if (propertys.Item2.TryGetValue(keyValuePair.Key, out var propertyInfo))
{
devices.Add(propertyInfo.Name, keyValuePair.Value?.ToString());
}

View File

@@ -1,5 +1,4 @@

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
@@ -9,9 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Mapster;
@@ -23,6 +19,7 @@ using MiniExcelLibs;
using SqlSugar;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Dynamic;
using System.Reflection;
@@ -315,35 +312,36 @@ public class VariableService : BaseService<Variable>, IVariableService
//总数据
Dictionary<string, object> sheets = new();
//变量页
List<Dictionary<string, object>> variableExports = new();
ConcurrentList<ConcurrentDictionary<string, object>> variableExports = new();
//变量附加属性转成Dict<表名,List<Dict<列名,列数据>>>的形式
Dictionary<string, List<Dictionary<string, object>>> devicePropertys = new();
ConcurrentDictionary<string, ConcurrentList<ConcurrentDictionary<string, object>>> devicePropertys = new();
ConcurrentDictionary<string, (VariablePropertyBase, Dictionary<string, PropertyInfo>)> propertysDict = new();
#region
var type = typeof(Variable);
var propertyInfos = type.GetProperties().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>() == null).OrderBy(
var propertyInfos = type.GetRuntimeProperties().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>() == null).OrderBy(
a =>
{
return a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? 999999;
}
);
).ToList();
#endregion
foreach (var variable in data)
data.ParallelForEach((variable, state, index) =>
{
Dictionary<string, object> varExport = new();
ConcurrentDictionary<string, object> varExport = new();
deviceDicts.TryGetValue(variable.DeviceId.Value, out var device);
//设备实体没有包含设备名称,手动插入
varExport.Add(ExportString.DeviceName, device?.Name ?? deviceName);
varExport.TryAdd(ExportString.DeviceName, device?.Name ?? deviceName);
foreach (var item in propertyInfos)
{
//描述
var desc = type.GetPropertyDisplayName(item.Name);
//数据源增加
varExport.Add(desc ?? item.Name, item.GetValue(variable)?.ToString());
varExport.TryAdd(desc ?? item.Name, item.GetValue(variable)?.ToString());
}
//添加完整设备信息
@@ -355,33 +353,41 @@ public class VariableService : BaseService<Variable>, IVariableService
{
//插件属性
//单个设备的行数据
Dictionary<string, object> driverInfo = new();
ConcurrentDictionary<string, object> driverInfo = new();
var has = deviceDicts.TryGetValue(item.Key, out var businessDevice);
if (!has)
continue;
//没有包含设备名称,手动插入
driverInfo.Add(ExportString.DeviceName, businessDevice.Name);
driverInfo.Add(ExportString.VariableName, variable.Name);
driverInfo.TryAdd(ExportString.DeviceName, businessDevice.Name);
driverInfo.TryAdd(ExportString.VariableName, variable.Name);
var propDict = item.Value;
var variableProperty = ((BusinessBase)_pluginService.GetDriver(businessDevice.PluginName)).VariablePropertys;
var variablePropertyType = variableProperty.GetType();
var propertys = variablePropertyType.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => variablePropertyType.GetPropertyDisplayName(a.Name));
if (propertysDict.TryGetValue(businessDevice.PluginName, out var propertys))
{
}
else
{
var variableProperty = ((BusinessBase)_pluginService.GetDriver(businessDevice.PluginName)).VariablePropertys;
propertys.Item1 = variableProperty;
var variablePropertyType = variableProperty.GetType();
propertys.Item2 = variablePropertyType.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => variablePropertyType.GetPropertyDisplayName(a.Name));
propertysDict.TryAdd(businessDevice.PluginName, propertys);
}
//根据插件的配置属性项生成列,从数据库中获取值或者获取属性默认值
foreach (var item1 in propertys)
foreach (var item1 in propertys.Item2)
{
if (propDict.TryGetValue(item1.Value.Name, out var dependencyProperty))
{
driverInfo.Add(item1.Key, dependencyProperty);
driverInfo.TryAdd(item1.Key, dependencyProperty);
}
else
{
//添加对应属性数据
driverInfo.Add(item1.Key, ThingsGatewayStringConverter.Default.Serialize(null, item1.Value.GetValue(variableProperty)));
driverInfo.TryAdd(item1.Key, ThingsGatewayStringConverter.Default.Serialize(null, item1.Value.GetValue(propertys.Item1)));
}
}
@@ -405,33 +411,27 @@ public class VariableService : BaseService<Variable>, IVariableService
}
#endregion sheet
}
});
//添加设备页
sheets.Add(ExportString.VariableName, variableExports);
//HASH
foreach (var item in devicePropertys)
{
HashSet<string> allKeys = new();
//HashSet<string> allKeys = item.Value.SelectMany(a => a.Keys).ToHashSet();
foreach (var dict in item.Value)
{
foreach (var key in dict.Keys)
{
allKeys.Add(key);
}
}
foreach (var dict in item.Value)
{
foreach (var key in allKeys)
{
if (!dict.ContainsKey(key))
{
// 添加缺失的键,并设置默认值
dict.Add(key, null);
}
}
}
//foreach (var dict in item.Value)
//{
// foreach (var key in allKeys)
// {
// if (!dict.ContainsKey(key))
// {
// // 添加缺失的键,并设置默认值
// dict.TryAdd(key, null);
// }
// }
//}
sheets.Add(item.Key, item.Value);
}
@@ -477,7 +477,6 @@ public class VariableService : BaseService<Variable>, IVariableService
// 获取所有设备的字典,以设备名称作为键
var deviceDicts = _deviceService.GetAll().ToDictionary(a => a.Name);
// 使用 MiniExcel 打开文件
using var db = GetDB();
// 从数据库中获取所有变量,并转换为字典,以变量名称作为键
@@ -493,6 +492,7 @@ public class VariableService : BaseService<Variable>, IVariableService
// 获取驱动插件的全名和名称的字典
var driverPluginFullNameDict = _pluginService.GetList().ToDictionary(a => a.FullName);
var driverPluginNameDict = _pluginService.GetList().ToDictionary(a => a.Name);
ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();
// 遍历每个工作表
foreach (var sheetName in sheetNames)
@@ -510,6 +510,10 @@ public class VariableService : BaseService<Variable>, IVariableService
// 线程安全的变量列表
var variables = new ConcurrentList<Variable>();
var type = typeof(Variable);
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var variableProperties = type.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => type.GetPropertyDisplayName(a.Name));
// 并行处理每一行数据
rows.ParallelForEach((item, state, index) =>
@@ -517,7 +521,7 @@ public class VariableService : BaseService<Variable>, IVariableService
try
{
// 尝试将行数据转换为 Variable 对象
var variable = ((ExpandoObject)item!).ConvertToEntity<Variable>(true);
var variable = ((ExpandoObject)item!).ConvertToEntity<Variable>(variableProperties);
variable.Row = index;
// 获取设备名称并查找对应的设备
@@ -609,18 +613,32 @@ public class VariableService : BaseService<Variable>, IVariableService
continue;
}
var variableProperty = ((BusinessBase)_pluginService.GetDriver(driverPluginType.FullName)).VariablePropertys;
var variablePropertyType = variableProperty.GetType();
var propertys = variablePropertyType.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => variablePropertyType.GetPropertyDisplayName(a.Name));
if (propertysDict.TryGetValue(driverPluginType.FullName, out var propertys))
{
}
else
{
var variableProperty = ((BusinessBase)_pluginService.GetDriver(driverPluginType.FullName)).VariablePropertys;
var variablePropertyType = variableProperty.GetType();
propertys.Item1 = variablePropertyType;
propertys.Item2 = variablePropertyType.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => variablePropertyType.GetPropertyDisplayName(a.Name));
// 获取目标类型的所有属性,并根据是否需要过滤 IgnoreExcelAttribute 进行筛选
var properties = propertys.Item1.GetRuntimeProperties().Where(a => (a.GetCustomAttribute<IgnoreExcelAttribute>() == null) && a.CanWrite)
.ToDictionary(a => propertys.Item1.GetPropertyDisplayName(a.Name));
propertys.Item3 = properties;
propertysDict.TryAdd(driverPluginType.FullName, propertys);
}
rows.ParallelForEach(item =>
{
try
{
// 尝试将导入的项转换为对象
var pluginProp = (item as ExpandoObject)?.ConvertToEntity(variablePropertyType, true);
var pluginProp = (item as ExpandoObject)?.ConvertToEntity(propertys.Item1, propertys.Item3);
// 如果转换失败,则添加错误信息到导入预览结果并返回
if (pluginProp == null)
@@ -676,7 +694,7 @@ public class VariableService : BaseService<Variable>, IVariableService
Dictionary<string, string> dependencyProperties = new();
foreach (var keyValuePair in item)
{
if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo))
if (propertys.Item2.TryGetValue(keyValuePair.Key, out var propertyInfo))
{
dependencyProperties.Add(propertyInfo.Name, keyValuePair.Value?.ToString());
}

View File

@@ -13,7 +13,7 @@
AllowResizing="true" IsFixedHeader=true IsMultipleSelect=true SearchModel=SearchModel
ShowExtendButtons=true ShowExportButton
ShowDefaultButtons=true ExtendButtonColumnWidth=150
OnQueryAsync="OnQueryAsync" IsPagination=false
OnQueryAsync="OnQueryAsync" IsPagination=true
OnSaveAsync="Save" OnDeleteAsync="Delete">
<TableColumns>

View File

@@ -46,9 +46,9 @@
</Button>
</ExportButtonDropdownTemplate>
<TableToolbarTemplate>
<TableToolbarButton TItem="Variable"
<TableToolbarPopConfirmButton TItem="Variable"
Color=Color.Warning Text="@Localizer["Clear"]"
IsAsync OnClickCallback=@(async(a)=>{ await VariableService.ClearVariableAsync();await ToastService.Default(); await table.QueryAsync(); }) />
IsAsync OnConfirm=@(async()=>{ await VariableService.ClearVariableAsync();await ToastService.Default(); await table.QueryAsync(); }) />
<PopConfirmButton TItem="Variable"
Color=Color.Warning Text="@Localizer["Test"]"