优化导入excel效率
This commit is contained in:
@@ -362,13 +362,13 @@ namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
var result1 = SendThenResponse(ISO_CR);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
Logger?.Error(client.IP + ":" + client.Port + "ISO初始化失败");
|
||||
Logger?.Warning(client.IP + ":" + client.Port + "-ISO初始化失败:" + result1.Message);
|
||||
return;
|
||||
}
|
||||
var result2 = SendThenResponse(S7_PN);
|
||||
if (!result2.IsSuccess)
|
||||
{
|
||||
Logger?.Error(client.IP + ":" + client.Port + "初始化失败");
|
||||
Logger?.Warning(client.IP + ":" + client.Port + "-初始化失败");
|
||||
return;
|
||||
}
|
||||
pdu_length = ThingsGatewayBitConverter.ToUInt16(result2.Content.SelectLast(2), 0);
|
||||
|
@@ -43,13 +43,14 @@ public abstract class S7 : CollectBase
|
||||
|
||||
public override Task AfterStopAsync()
|
||||
{
|
||||
_plc?.Disconnect();
|
||||
if (_plc != null)
|
||||
_plc?.Disconnect();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task BeforStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _plc.ConnectAsync(cancellationToken);
|
||||
await _plc?.ConnectAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
@@ -18,13 +18,12 @@ public class ImportPreviewOutputBase
|
||||
/// 是否有错误
|
||||
/// </summary>
|
||||
public bool HasError { get; set; }
|
||||
public virtual int DataCount { get; }
|
||||
public virtual List<(int row, bool isSuccess, string resultString)> Results { get; set; } = new();
|
||||
public int DataCount { get => Results.Count; }
|
||||
public TGConcurrentList<(int row, bool isSuccess, string resultString)> Results { get; set; } = new();
|
||||
}
|
||||
public class ImportPreviewOutput<T> : ImportPreviewOutputBase where T : class
|
||||
{
|
||||
public override int DataCount { get => Data == null ? 0 : Data.Count; }
|
||||
public List<T> Data { get; set; } = new();
|
||||
public Dictionary<string, T> Data { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
|
688
src/ThingsGateway.Core/BaseOutput/TGConcurrentList.cs
Normal file
688
src/ThingsGateway.Core/BaseOutput/TGConcurrentList.cs
Normal file
@@ -0,0 +1,688 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:https://www.yuque.com/rrqm/touchsocket/index
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
using System.Linq;
|
||||
|
||||
namespace ThingsGateway.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 线程安全的List,其基本操作和List一致。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class TGConcurrentList<T> : IList<T>
|
||||
{
|
||||
private readonly List<T> m_list;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
public TGConcurrentList(IEnumerable<T> collection)
|
||||
{
|
||||
m_list = new List<T>(collection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public TGConcurrentList()
|
||||
{
|
||||
m_list = new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="capacity"></param>
|
||||
public TGConcurrentList(int capacity)
|
||||
{
|
||||
m_list = new List<T>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 元素数量
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为只读
|
||||
/// </summary>
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// 获取索引元素
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list[index];
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list[index] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加元素
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public void Add(T item)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有元素
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含某个元素
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制到
|
||||
/// </summary>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="arrayIndex"></param>
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回迭代器
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.ToList().GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回迭代器组合
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 索引
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.IndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="item"></param>
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Insert(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除元素
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按索引移除
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
if (index < m_list.Count)
|
||||
{
|
||||
m_list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置容量
|
||||
/// </summary>
|
||||
public int Capacity
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.Capacity;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Capacity = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.AddRange(IEnumerable{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
public void AddRange(IEnumerable<T> collection)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.AddRange(collection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.BinarySearch(T)"/>
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public int BinarySearch(T item)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.BinarySearch(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.BinarySearch(T, IComparer{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="comparer"></param>
|
||||
/// <returns></returns>
|
||||
public int BinarySearch(T item, IComparer<T> comparer)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.BinarySearch(item, comparer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.BinarySearch(int, int, T, IComparer{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="comparer"></param>
|
||||
/// <returns></returns>
|
||||
public int BinarySearch(int index, int count, T item, IComparer<T> comparer)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.BinarySearch(index, count, item, comparer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.ConvertAll{TOutput}(Converter{T, TOutput})"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TOutput"></typeparam>
|
||||
/// <param name="converter"></param>
|
||||
/// <returns></returns>
|
||||
public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.ConvertAll(converter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.Find(Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public T Find(Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.Find(match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.FindAll(Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public List<T> FindAll(Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.FindAll(match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.FindIndex(int, int, Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="startIndex"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public int FindIndex(int startIndex, int count, Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.FindIndex(startIndex, count, match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.FindIndex(int, Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="startIndex"></param>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public int FindIndex(int startIndex, Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.FindIndex(startIndex, match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.FindIndex(Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public int FindIndex(Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.FindIndex(match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.FindLast(Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public T FindLast(Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.FindLast(match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.FindLastIndex(int, int, Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="startIndex"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public int FindLastIndex(int startIndex, int count, Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.FindLastIndex(startIndex, count, match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.FindLastIndex(int, Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="startIndex"></param>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public int FindLastIndex(int startIndex, Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.FindLastIndex(startIndex, match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.FindLastIndex(Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public int FindLastIndex(Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.FindLastIndex(match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.ForEach(Action{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public void ForEach(Action<T> action)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.ForEach(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.GetRange(int, int)"/>
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <returns></returns>
|
||||
public List<T> GetRange(int index, int count)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.GetRange(index, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.IndexOf(T, int)"/>
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public int IndexOf(T item, int index)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.IndexOf(item, index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.IndexOf(T, int, int)"/>
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <returns></returns>
|
||||
public int IndexOf(T item, int index, int count)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.IndexOf(item, index, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.InsertRange(int, IEnumerable{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="collection"></param>
|
||||
public void InsertRange(int index, IEnumerable<T> collection)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.InsertRange(index, collection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.LastIndexOf(T)"/>
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public int LastIndexOf(T item)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.IndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.LastIndexOf(T, int)"/>
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public int LastIndexOf(T item, int index)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.LastIndexOf(item, index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.LastIndexOf(T, int, int)"/>
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <returns></returns>
|
||||
public int LastIndexOf(T item, int index, int count)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.LastIndexOf(item, index, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.RemoveAll(Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
public void RemoveAll(Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.RemoveAll(match);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.RemoveRange(int, int)"/>
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="count"></param>
|
||||
public void RemoveRange(int index, int count)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.RemoveRange(index, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.Reverse()"/>
|
||||
/// </summary>
|
||||
public void Reverse()
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.Reverse(int, int)"/>
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="count"></param>
|
||||
public void Reverse(int index, int count)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Reverse(index, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.Sort()"/>
|
||||
/// </summary>
|
||||
public void Sort()
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.Sort(Comparison{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="comparison"></param>
|
||||
public void Sort(Comparison<T> comparison)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Sort(comparison);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.Sort(IComparer{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="comparer"></param>
|
||||
public void Sort(IComparer<T> comparer)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Sort(comparer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.Sort(int, int, IComparer{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="comparer"></param>
|
||||
public void Sort(int index, int count, IComparer<T> comparer)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.Sort(index, count, comparer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.ToArray"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.TrimExcess"/>
|
||||
/// </summary>
|
||||
public void TrimExcess()
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
m_list.TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.TrueForAll(Predicate{T})"/>
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public bool TrueForAll(Predicate<T> match)
|
||||
{
|
||||
lock (((ICollection)m_list).SyncRoot)
|
||||
{
|
||||
return m_list.TrueForAll(match);
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,7 +11,6 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
@@ -473,16 +472,6 @@ namespace ThingsGateway.Core.Extension
|
||||
/// <returns></returns>
|
||||
public static async Task ForeachAsync<T>(this IEnumerable<T> source, Func<T, Task> action, int maxParallelCount, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
await action(item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var list = new List<Task>();
|
||||
foreach (var item in source)
|
||||
{
|
||||
@@ -512,13 +501,7 @@ namespace ThingsGateway.Core.Extension
|
||||
/// <returns></returns>
|
||||
public static Task ForeachAsync<T>(this IEnumerable<T> source, Func<T, Task> action, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (source is ICollection<T> collection)
|
||||
{
|
||||
return ForeachAsync(collection, action, collection.Count, cancellationToken);
|
||||
}
|
||||
|
||||
var list = source.ToList();
|
||||
return ForeachAsync(list, action, list.Count, cancellationToken);
|
||||
return ForeachAsync(source, action, source.Count(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -621,16 +604,7 @@ namespace ThingsGateway.Core.Extension
|
||||
public static async Task ForAsync<T>(this IEnumerable<T> source, Func<T, int, Task> selector, int maxParallelCount, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int index = 0;
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
await selector(item, index);
|
||||
index++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var list = new List<Task>();
|
||||
foreach (var item in source)
|
||||
|
@@ -172,7 +172,7 @@ public class OperResult : IRequestInfo, IOperResult
|
||||
/// </summary>
|
||||
public string Exception { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => ResultCode.HasFlag(ResultCode.Success);
|
||||
public bool IsSuccess => ResultCode == ResultCode.Success;
|
||||
/// <inheritdoc/>
|
||||
public string Message { get; set; }
|
||||
/// <inheritdoc/>
|
||||
|
@@ -10,6 +10,8 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Core;
|
||||
|
||||
namespace ThingsGateway.Web.Foundation;
|
||||
@@ -73,7 +75,7 @@ public class MemoryVariable : BaseEntity
|
||||
/// 变量额外属性Json,通常使用为上传设备,List属性
|
||||
/// </summary>
|
||||
[SugarColumn(IsJson = true, ColumnName = "VariablePropertys", ColumnDescription = "变量属性Json", IsNullable = true)]
|
||||
public Dictionary<long, List<DependencyProperty>> VariablePropertys { get; set; } = new();
|
||||
public ConcurrentDictionary<long, List<DependencyProperty>> VariablePropertys { get; set; } = new();
|
||||
|
||||
|
||||
|
||||
|
@@ -15,6 +15,8 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Core;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Web.Foundation;
|
||||
/// <summary>
|
||||
/// 动态类型扩展
|
||||
@@ -31,19 +33,18 @@ public static class ExpandoObjectHelpers
|
||||
public static T ConvertToEntity<T>(this ExpandoObject expandoObject, bool filter) where T : new()
|
||||
{
|
||||
var entity = new T();
|
||||
var properties = typeof(T).GetAllProps().Where(a => !filter || a.GetCustomAttribute<ExcelAttribute>() != null);
|
||||
var properties = typeof(T).GetAllProps().Where(a => !filter || a.GetCustomAttribute<ExcelAttribute>() != null).ToDictionary(a => a.FindDisplayAttribute());
|
||||
|
||||
foreach (var keyValuePair in (IDictionary<string, object>)expandoObject)
|
||||
expandoObject.ForEach(keyValuePair =>
|
||||
{
|
||||
var property = properties.FirstOrDefault(p => p.FindDisplayAttribute() == keyValuePair.Key);
|
||||
if (property != null)
|
||||
if (properties.TryGetValue(keyValuePair.Key, out var property))
|
||||
{
|
||||
var value = keyValuePair.Value;
|
||||
var objValue = property.ObjToTypeValue(value?.ToString() ?? "");
|
||||
property.SetValue(entity, objValue);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,7 @@ public static class ParallelHelpers
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="body"></param>
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body) where T : class
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body)
|
||||
{
|
||||
Parallel.ForEach(source, _options, variable =>
|
||||
{
|
||||
@@ -36,4 +36,16 @@ public static class ParallelHelpers
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行<see cref="Parallel.ForEach{TSource}(IEnumerable{TSource}, Action{TSource})"/>
|
||||
/// </summary>
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body, int parallelCount)
|
||||
{
|
||||
var options = new ParallelOptions();
|
||||
options.MaxDegreeOfParallelism = parallelCount / 2 == 0 ? 1 : parallelCount;
|
||||
Parallel.ForEach(source, options, variable =>
|
||||
{
|
||||
body(variable);
|
||||
});
|
||||
}
|
||||
}
|
@@ -16,13 +16,14 @@ using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
using MiniExcelLibs;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Core;
|
||||
using ThingsGateway.Core.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Web.Foundation;
|
||||
|
||||
@@ -234,29 +235,27 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
if (devId == 0)
|
||||
{
|
||||
var devices = GetCacheList().Where(a => a.Enable).ToList();
|
||||
var runtime = devices.Adapt<List<CollectDeviceRunTime>>();
|
||||
var runtime = devices.Adapt<List<CollectDeviceRunTime>>().ToDictionary(a => a.Id);
|
||||
using var serviceScope = _scopeFactory.CreateScope();
|
||||
var variableService = serviceScope.ServiceProvider.GetService<IVariableService>();
|
||||
var collectVariableRunTimes = await variableService.GetDeviceVariableRuntimeAsync();
|
||||
await runtime.ForeachAsync(device =>
|
||||
{
|
||||
var pluginName = _driverPluginService.GetNameById(device.PluginId);
|
||||
device.PluginName = pluginName;
|
||||
device.DeviceVariableRunTimes = collectVariableRunTimes.Where(a => a.DeviceId == device.Id).ToList();
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
ConcurrentDictionary<long, DriverPlugin> driverPlugins = new(_driverPluginService.GetCacheList().ToDictionary(a => a.Id));
|
||||
runtime.Values.ParallelForEach(device =>
|
||||
{
|
||||
driverPlugins.TryGetValue(device.PluginId, out var driverPlugin);
|
||||
device.PluginName = driverPlugin?.AssembleName;
|
||||
device.DeviceVariableRunTimes = collectVariableRunTimes.Where(a => a.DeviceId == device.Id).ToList();
|
||||
});
|
||||
|
||||
await collectVariableRunTimes.ForeachAsync(variable =>
|
||||
{
|
||||
var device = runtime.FirstOrDefault(a => a.Id == variable.DeviceId);
|
||||
if (device != null)
|
||||
{
|
||||
variable.CollectDeviceRunTime = device;
|
||||
variable.DeviceName = device.Name;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
return runtime;
|
||||
collectVariableRunTimes.ParallelForEach(variable =>
|
||||
{
|
||||
if (runtime.TryGetValue(variable.DeviceId, out var device))
|
||||
{
|
||||
variable.CollectDeviceRunTime = device;
|
||||
variable.DeviceName = device.Name;
|
||||
}
|
||||
});
|
||||
return runtime.Values.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -270,12 +269,11 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
runtime.PluginName = pluginName;
|
||||
runtime.DeviceVariableRunTimes = collectVariableRunTimes;
|
||||
|
||||
await collectVariableRunTimes.ForeachAsync(variable =>
|
||||
{
|
||||
variable.CollectDeviceRunTime = runtime;
|
||||
variable.DeviceName = runtime.Name;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
collectVariableRunTimes.ParallelForEach(variable =>
|
||||
{
|
||||
variable.CollectDeviceRunTime = runtime;
|
||||
variable.DeviceName = runtime.Name;
|
||||
});
|
||||
return new() { runtime };
|
||||
|
||||
}
|
||||
@@ -291,12 +289,13 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
devDatas ??= GetCacheList();
|
||||
|
||||
//总数据
|
||||
Dictionary<string, object> sheets = new Dictionary<string, object>();
|
||||
Dictionary<string, object> sheets = new();
|
||||
//设备页
|
||||
List<Dictionary<string, object>> devExports = new();
|
||||
//设备附加属性,转成Dict<表名,List<Dict<列名,列数据>>>的形式
|
||||
Dictionary<string, List<Dictionary<string, object>>> devicePropertys = new();
|
||||
|
||||
var driverPluginDicts = _driverPluginService.GetCacheList().ToDictionary(a => a.Id);
|
||||
var deviceDicts = devDatas.ToDictionary(a => a.Id);
|
||||
foreach (var devData in devDatas)
|
||||
{
|
||||
#region 设备sheet
|
||||
@@ -310,10 +309,13 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
//数据源增加
|
||||
devExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString());
|
||||
}
|
||||
driverPluginDicts.TryGetValue(devData.PluginId, out var driverPlugin);
|
||||
deviceDicts.TryGetValue(devData.RedundantDeviceId, out var redundantDevice);
|
||||
|
||||
//设备实体没有包含插件名称,手动插入
|
||||
devExport.Add(ExportHelpers.PluginName, _driverPluginService.GetNameById(devData.PluginId));
|
||||
devExport.Add(ExportHelpers.PluginName, driverPlugin.AssembleName);
|
||||
//设备实体没有包含冗余设备名称,手动插入
|
||||
devExport.Add(ExportHelpers.RedundantDeviceName, GetNameById(devData.RedundantDeviceId));
|
||||
devExport.Add(ExportHelpers.RedundantDeviceName, redundantDevice?.Name);
|
||||
|
||||
//添加完整设备信息
|
||||
devExports.Add(devExport);
|
||||
@@ -323,7 +325,7 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
#region 插件sheet
|
||||
//插件属性
|
||||
//单个设备的行数据
|
||||
Dictionary<string, object> driverInfo = new Dictionary<string, object>();
|
||||
Dictionary<string, object> driverInfo = new();
|
||||
//没有包含设备名称,手动插入
|
||||
if (devData.DevicePropertys.Count > 0)
|
||||
{
|
||||
@@ -336,7 +338,7 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
}
|
||||
|
||||
//插件名称去除首部ThingsGateway.作为表名
|
||||
var pluginName = _driverPluginService.GetNameById(devData.PluginId).Replace(ExportHelpers.PluginLeftName, "");
|
||||
var pluginName = driverPlugin.AssembleName.Replace(ExportHelpers.PluginLeftName, "");
|
||||
if (devicePropertys.ContainsKey(pluginName))
|
||||
{
|
||||
if (driverInfo.Count > 0)
|
||||
@@ -372,6 +374,8 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
using var stream = file.OpenReadStream(512000000);
|
||||
await stream.CopyToAsync(fs);
|
||||
var sheetNames = MiniExcel.GetSheetNames(fs);
|
||||
var deviceDicts = GetCacheList().ToDictionary(a => a.Name);
|
||||
var pluginDicts = _driverPluginService.GetCacheList().ToDictionary(a => a.AssembleName);
|
||||
|
||||
//导入检验结果
|
||||
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
|
||||
@@ -380,7 +384,7 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
foreach (var sheetName in sheetNames)
|
||||
{
|
||||
//单页数据
|
||||
var rows = (await fs.QueryAsync(useHeaderRow: true, sheetName: sheetName)).Cast<IDictionary<string, object>>();
|
||||
var rows = fs.Query(useHeaderRow: true, sheetName: sheetName).Cast<IDictionary<string, object>>();
|
||||
#region 采集设备sheet
|
||||
if (sheetName == ExportHelpers.CollectDeviceSheetName)
|
||||
{
|
||||
@@ -388,55 +392,57 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
ImportPreviewOutput<CollectDevice> importPreviewOutput = new();
|
||||
ImportPreviews.Add(sheetName, importPreviewOutput);
|
||||
deviceImportPreview = importPreviewOutput;
|
||||
List<CollectDevice> devices = new();
|
||||
rows.ForEach(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var device = ((ExpandoObject)item).ConvertToEntity<CollectDevice>(true);
|
||||
//var hasDup = rows.HasDuplicateElements<DeviceVariable>(nameof(UploadDevice.Name), device.Name);
|
||||
//var hasName = GetIdByName(device.Name) > 0;
|
||||
//if (hasDup || hasName)
|
||||
//{
|
||||
// importPreviewOutput.HasError = true;
|
||||
// importPreviewOutput.Results.Add((false, "名称重复"));
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
#region 特殊转化名称
|
||||
//转化插件名称
|
||||
var hasPlugin = item.TryGetValue(ExportHelpers.PluginName, out var pluginObj);
|
||||
|
||||
List<CollectDevice> devices = new List<CollectDevice>();
|
||||
await rows.ForeachAsync(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pluginObj == null || !pluginDicts.TryGetValue(pluginObj.ToString(), out var plugin))
|
||||
{
|
||||
//找不到对应的插件
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, $"{ExportHelpers.PluginName}不存在"));
|
||||
return;
|
||||
}
|
||||
//转化冗余设备名称
|
||||
var hasRedundant = item.TryGetValue(ExportHelpers.PluginName, out var redundantObj);
|
||||
|
||||
var device = ((ExpandoObject)item).ConvertToEntity<CollectDevice>(true);
|
||||
//var hasDup = rows.HasDuplicateElements<DeviceVariable>(nameof(UploadDevice.Name), device.Name);
|
||||
//var hasName = GetIdByName(device.Name) > 0;
|
||||
//if (hasDup || hasName)
|
||||
//{
|
||||
// importPreviewOutput.HasError = true;
|
||||
// importPreviewOutput.Results.Add((false, "名称重复"));
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
#region 特殊转化名称
|
||||
//转化插件名称
|
||||
var pluginName = item.FirstOrDefault(a => a.Key == ExportHelpers.PluginName).Value;
|
||||
if (_driverPluginService.GetIdByName(pluginName?.ToString()) == null)
|
||||
{
|
||||
//找不到对应的插件
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, $"{ExportHelpers.PluginName}不存在"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
//转化冗余设备名称
|
||||
var redundantDeviceName = item.FirstOrDefault(a => a.Key == ExportHelpers.RedundantDeviceName).Value;
|
||||
#endregion
|
||||
//插件ID、设备ID、冗余设备ID都需要手动补录
|
||||
device.PluginId = plugin.Id;
|
||||
if (hasRedundant && deviceDicts.TryGetValue(redundantObj.ToString(), out var rendundantDevice))
|
||||
{
|
||||
device.RedundantDeviceId = rendundantDevice.Id;
|
||||
}
|
||||
device.Id = deviceDicts.TryGetValue(device.Name, out var collectDevice) ? collectDevice.Id : YitIdHelper.NextId();
|
||||
|
||||
#endregion
|
||||
//插件ID、设备ID、冗余设备ID都需要手动补录
|
||||
device.PluginId = _driverPluginService.GetIdByName(pluginName.ToString()).ToLong();
|
||||
device.RedundantDeviceId = GetIdByName(redundantDeviceName?.ToString()).ToLong();
|
||||
device.Id = this.GetIdByName(device.Name) ?? YitIdHelper.NextId();
|
||||
devices.Add(device);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
devices.Add(device);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
importPreviewOutput.Data = devices;
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return;
|
||||
}
|
||||
});
|
||||
importPreviewOutput.Data = devices.ToDictionary(a => a.Name);
|
||||
|
||||
}
|
||||
#endregion
|
||||
@@ -459,49 +465,43 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
using var serviceScope = _scopeFactory.CreateScope();
|
||||
var pluginSingletonService = serviceScope.ServiceProvider.GetService<PluginSingletonService>();
|
||||
var driver = (DriverBase)pluginSingletonService.GetDriver(YitIdHelper.NextId(), driverPlugin);
|
||||
var propertys = driver.DriverPropertys.GetType().GetAllProps().Where(a => a.GetCustomAttribute<DevicePropertyAttribute>() != null);
|
||||
await rows.ForeachAsync(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var propertys = driver.DriverPropertys.GetType().GetAllProps()
|
||||
.Where(a => a.GetCustomAttribute<DevicePropertyAttribute>() != null)
|
||||
.ToDictionary(a => a.FindDisplayAttribute(a => a.GetCustomAttribute<DevicePropertyAttribute>()?.Description));
|
||||
rows.ForEach(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
List<DependencyProperty> devices = new List<DependencyProperty>();
|
||||
foreach (var item1 in item)
|
||||
{
|
||||
var propertyInfo = propertys.FirstOrDefault(p => p.FindDisplayAttribute(a => a.GetCustomAttribute<DevicePropertyAttribute>()?.Description) == item1.Key);
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
//不存在时不报错
|
||||
}
|
||||
else
|
||||
{
|
||||
devices.Add(new()
|
||||
{
|
||||
PropertyName = propertyInfo.Name,
|
||||
Description = item1.Key.ToString(),
|
||||
Value = item1.Value?.ToString()
|
||||
});
|
||||
}
|
||||
List<DependencyProperty> devices = new();
|
||||
foreach (var keyValuePair in item)
|
||||
{
|
||||
if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo))
|
||||
{
|
||||
devices.Add(new()
|
||||
{
|
||||
PropertyName = propertyInfo.Name,
|
||||
Description = keyValuePair.Key.ToString(),
|
||||
Value = keyValuePair.Value?.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
//转化插件名称
|
||||
var value = item.FirstOrDefault(a => a.Key == ExportHelpers.DeviceName);
|
||||
if (deviceImportPreview.Data?.Any(it => it.Name == value.Value.ToString()) == true)
|
||||
{
|
||||
var deviceId = this.GetIdByName(value.Value.ToString()) ?? deviceImportPreview.Data.FirstOrDefault(it => it.Name == value.Value.ToString()).Id;
|
||||
deviceImportPreview.Data.FirstOrDefault(a => a.Id == deviceId).DevicePropertys = devices;
|
||||
}
|
||||
importPreviewOutput.Data.Add(string.Empty);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
}
|
||||
//转化插件名称
|
||||
|
||||
var value = item[ExportHelpers.DeviceName];
|
||||
|
||||
deviceImportPreview.Data[value.ToString()].DevicePropertys = devices;
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -521,7 +521,7 @@ public class CollectDeviceService : DbRepository<CollectDevice>, ICollectDeviceS
|
||||
if (item.Key == ExportHelpers.CollectDeviceSheetName)
|
||||
{
|
||||
var collectDeviceImports = ((ImportPreviewOutput<CollectDevice>)item.Value).Data;
|
||||
collectDevices = collectDeviceImports.Adapt<List<CollectDevice>>();
|
||||
collectDevices = collectDeviceImports.Values.Adapt<List<CollectDevice>>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -16,13 +16,14 @@ using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
using MiniExcelLibs;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Core;
|
||||
using ThingsGateway.Core.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Web.Foundation;
|
||||
|
||||
@@ -162,30 +163,31 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
public List<UploadDevice> GetCacheList()
|
||||
{
|
||||
//先从Cache拿
|
||||
var collectDevice = _sysCacheService.Get<List<UploadDevice>>(ThingsGatewayCacheConst.Cache_UploadDevice, "");
|
||||
if (collectDevice == null)
|
||||
var uploadDevice = _sysCacheService.Get<List<UploadDevice>>(ThingsGatewayCacheConst.Cache_UploadDevice, "");
|
||||
if (uploadDevice == null)
|
||||
{
|
||||
collectDevice = Context.Queryable<UploadDevice>().ToList();
|
||||
if (collectDevice != null)
|
||||
uploadDevice = Context.Queryable<UploadDevice>().ToList();
|
||||
if (uploadDevice != null)
|
||||
{
|
||||
//插入Cache
|
||||
_sysCacheService.Set(ThingsGatewayCacheConst.Cache_UploadDevice, "", collectDevice);
|
||||
_sysCacheService.Set(ThingsGatewayCacheConst.Cache_UploadDevice, "", uploadDevice);
|
||||
}
|
||||
}
|
||||
return collectDevice;
|
||||
return uploadDevice;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<UploadDeviceRunTime> GetUploadDeviceRuntime(long devId = 0)
|
||||
{
|
||||
ConcurrentDictionary<long, DriverPlugin> driverPlugins = new(_driverPluginService.GetCacheList().ToDictionary(a => a.Id));
|
||||
if (devId == 0)
|
||||
{
|
||||
var devices = GetCacheList().Where(a => a.Enable).ToList();
|
||||
var runtime = devices.Adapt<List<UploadDeviceRunTime>>();
|
||||
foreach (var device in runtime)
|
||||
{
|
||||
var pluginName = _driverPluginService.GetNameById(device.PluginId);
|
||||
device.PluginName = pluginName;
|
||||
driverPlugins.TryGetValue(device.PluginId, out var driverPlugin);
|
||||
device.PluginName = driverPlugin?.AssembleName;
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
@@ -196,8 +198,8 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
var runtime = devices.Adapt<List<UploadDeviceRunTime>>();
|
||||
foreach (var device in runtime)
|
||||
{
|
||||
var pluginName = _driverPluginService.GetNameById(device.PluginId);
|
||||
device.PluginName = pluginName;
|
||||
driverPlugins.TryGetValue(device.PluginId, out var driverPlugin);
|
||||
device.PluginName = driverPlugin?.AssembleName;
|
||||
}
|
||||
return runtime;
|
||||
|
||||
@@ -214,7 +216,7 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
devDatas ??= GetCacheList();
|
||||
|
||||
//总数据
|
||||
Dictionary<string, object> sheets = new Dictionary<string, object>();
|
||||
Dictionary<string, object> sheets = new();
|
||||
//设备页
|
||||
List<Dictionary<string, object>> devExports = new();
|
||||
//设备附加属性,转成Dict<表名,List<Dict<列名,列数据>>>的形式
|
||||
@@ -244,7 +246,7 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
#region 插件sheet
|
||||
//插件属性
|
||||
//单个设备的行数据
|
||||
Dictionary<string, object> driverInfo = new Dictionary<string, object>();
|
||||
Dictionary<string, object> driverInfo = new();
|
||||
//没有包含设备名称,手动插入
|
||||
if (devData.DevicePropertys.Count > 0)
|
||||
{
|
||||
@@ -293,6 +295,8 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
using var stream = file.OpenReadStream(512000000);
|
||||
await stream.CopyToAsync(fs);
|
||||
var sheetNames = MiniExcel.GetSheetNames(fs);
|
||||
var deviceDicts = GetCacheList().ToDictionary(a => a.Name);
|
||||
var pluginDicts = _driverPluginService.GetCacheList().ToDictionary(a => a.AssembleName);
|
||||
|
||||
//导入检验结果
|
||||
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
|
||||
@@ -301,7 +305,7 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
foreach (var sheetName in sheetNames)
|
||||
{
|
||||
//单页数据
|
||||
var rows = (await fs.QueryAsync(useHeaderRow: true, sheetName: sheetName)).Cast<IDictionary<string, object>>();
|
||||
var rows = (fs.Query(useHeaderRow: true, sheetName: sheetName)).Cast<IDictionary<string, object>>();
|
||||
#region 上传设备sheet
|
||||
if (sheetName == ExportHelpers.UploadDeviceSheetName)
|
||||
{
|
||||
@@ -310,47 +314,48 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
ImportPreviews.Add(sheetName, importPreviewOutput);
|
||||
deviceImportPreview = importPreviewOutput;
|
||||
|
||||
List<UploadDevice> devices = new List<UploadDevice>();
|
||||
await rows.ForeachAsync(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = ((ExpandoObject)item).ConvertToEntity<UploadDevice>(true);
|
||||
//var hasDup = rows.HasDuplicateElements<DeviceVariable>(nameof(UploadDevice.Name), device.Name);
|
||||
//var hasName = GetIdByName(device.Name) > 0;
|
||||
//if (hasDup || hasName)
|
||||
//{
|
||||
// importPreviewOutput.HasError = true;
|
||||
// importPreviewOutput.Results.Add((false, "名称重复"));
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
#region 特殊转化名称
|
||||
//转化插件名称
|
||||
var pluginName = item.FirstOrDefault(a => a.Key == ExportHelpers.PluginName).Value;
|
||||
if (_driverPluginService.GetIdByName(pluginName?.ToString()) == null)
|
||||
{
|
||||
//找不到对应的插件
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, $"{ExportHelpers.PluginName}不存在"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
#endregion
|
||||
//插件ID、设备ID都需要手动补录
|
||||
device.PluginId = _driverPluginService.GetIdByName(pluginName?.ToString()).ToLong();
|
||||
device.Id = this.GetIdByName(device.Name) ?? YitIdHelper.NextId();
|
||||
List<UploadDevice> devices = new();
|
||||
rows.ForEach(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = ((ExpandoObject)item).ConvertToEntity<UploadDevice>(true);
|
||||
//var hasDup = rows.HasDuplicateElements<DeviceVariable>(nameof(UploadDevice.Name), device.Name);
|
||||
//var hasName = GetIdByName(device.Name) > 0;
|
||||
//if (hasDup || hasName)
|
||||
//{
|
||||
// importPreviewOutput.HasError = true;
|
||||
// importPreviewOutput.Results.Add((false, "名称重复"));
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
#region 特殊转化名称
|
||||
//转化插件名称
|
||||
var hasPlugin = item.TryGetValue(ExportHelpers.PluginName, out var pluginObj);
|
||||
|
||||
devices.Add(device);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
importPreviewOutput.Data = devices;
|
||||
if (pluginObj == null || !pluginDicts.TryGetValue(pluginObj.ToString(), out var plugin))
|
||||
{
|
||||
//找不到对应的插件
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, $"{ExportHelpers.PluginName}不存在"));
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
//插件ID、设备ID都需要手动补录
|
||||
device.PluginId = plugin.Id;
|
||||
device.Id = deviceDicts.TryGetValue(device.Name, out var uploadDevice) ? uploadDevice.Id : YitIdHelper.NextId();
|
||||
|
||||
devices.Add(device);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return;
|
||||
}
|
||||
});
|
||||
importPreviewOutput.Data = devices.ToDictionary(a => a.Name);
|
||||
|
||||
}
|
||||
#endregion
|
||||
@@ -374,51 +379,43 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
using var serviceScope = _scopeFactory.CreateScope();
|
||||
var pluginSingletonService = serviceScope.ServiceProvider.GetService<PluginSingletonService>();
|
||||
var driver = (DriverBase)pluginSingletonService.GetDriver(YitIdHelper.NextId(), driverPlugin);
|
||||
var propertys = driver.DriverPropertys.GetType().GetAllProps().Where(a => a.GetCustomAttribute<DevicePropertyAttribute>() != null);
|
||||
await rows.ForeachAsync(item =>
|
||||
{
|
||||
try
|
||||
var propertys = driver.DriverPropertys.GetType().GetAllProps()
|
||||
.Where(a => a.GetCustomAttribute<DevicePropertyAttribute>() != null)
|
||||
.ToDictionary(a => a.FindDisplayAttribute(a => a.GetCustomAttribute<DevicePropertyAttribute>()?.Description));
|
||||
rows.ForEach(item =>
|
||||
{
|
||||
|
||||
|
||||
List<DependencyProperty> devices = new List<DependencyProperty>();
|
||||
foreach (var item1 in item)
|
||||
try
|
||||
{
|
||||
var propertyInfo = propertys.FirstOrDefault(p => p.FindDisplayAttribute(a => a.GetCustomAttribute<DevicePropertyAttribute>()?.Description) == item1.Key);
|
||||
if (propertyInfo == null)
|
||||
|
||||
|
||||
List<DependencyProperty> devices = new();
|
||||
foreach (var keyValuePair in item)
|
||||
{
|
||||
//不存在时不报错
|
||||
}
|
||||
else
|
||||
{
|
||||
devices.Add(new()
|
||||
if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo))
|
||||
{
|
||||
PropertyName = propertyInfo.Name,
|
||||
Description = item1.Key.ToString(),
|
||||
Value = item1.Value?.ToString()
|
||||
});
|
||||
devices.Add(new()
|
||||
{
|
||||
PropertyName = propertyInfo.Name,
|
||||
Description = keyValuePair.Key.ToString(),
|
||||
Value = keyValuePair.Value?.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
//转化设备名称
|
||||
var value = item[ExportHelpers.DeviceName];
|
||||
|
||||
deviceImportPreview.Data[value.ToString()].DevicePropertys = devices;
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return;
|
||||
}
|
||||
//转化设备名称
|
||||
var deviceName = item.FirstOrDefault(a => a.Key == ExportHelpers.DeviceName).Value;
|
||||
if (deviceImportPreview.Data?.Any(it => it.Name == deviceName.ToString()) == true)
|
||||
catch (Exception ex)
|
||||
{
|
||||
var deviceId = this.GetIdByName(deviceName.ToString()) ?? deviceImportPreview.Data.FirstOrDefault(it => it.Name == deviceName.ToString()).Id;
|
||||
deviceImportPreview.Data.FirstOrDefault(a => a.Id == deviceId).DevicePropertys = devices;
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return;
|
||||
}
|
||||
importPreviewOutput.Data.Add(string.Empty);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -432,18 +429,18 @@ public class UploadDeviceService : DbRepository<UploadDevice>, IUploadDeviceServ
|
||||
[OperDesc("导入上传设备表", IsRecordPar = false)]
|
||||
public async Task ImportAsync(Dictionary<string, ImportPreviewOutputBase> input)
|
||||
{
|
||||
var collectDevices = new List<UploadDevice>();
|
||||
var uploadDevices = new List<UploadDevice>();
|
||||
foreach (var item in input)
|
||||
{
|
||||
if (item.Key == ExportHelpers.UploadDeviceSheetName)
|
||||
{
|
||||
var collectDeviceImports = ((ImportPreviewOutput<UploadDevice>)item.Value).Data;
|
||||
collectDevices = collectDeviceImports.Adapt<List<UploadDevice>>();
|
||||
var uploadDeviceImports = ((ImportPreviewOutput<UploadDevice>)item.Value).Data;
|
||||
uploadDevices = uploadDeviceImports.Values.Adapt<List<UploadDevice>>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
await Context.Storageable(collectDevices).ExecuteCommandAsync();
|
||||
_sysCacheService.Remove(ThingsGatewayCacheConst.Cache_CollectDevice, "");//cache删除
|
||||
await Context.Storageable(uploadDevices).ExecuteCommandAsync();
|
||||
_sysCacheService.Remove(ThingsGatewayCacheConst.Cache_UploadDevice, "");//cache删除
|
||||
|
||||
}
|
||||
|
||||
|
@@ -70,7 +70,7 @@ public interface IVariableService : ITransient
|
||||
/// <summary>
|
||||
/// 根据名称获取ID
|
||||
/// </summary>
|
||||
long GetIdByName(string name, bool onlyCache = true);
|
||||
long GetIdByName(string name);
|
||||
/// <summary>
|
||||
/// 获取中间变量运行态
|
||||
/// </summary>
|
||||
@@ -80,7 +80,7 @@ public interface IVariableService : ITransient
|
||||
/// <summary>
|
||||
/// 根据ID获取名称
|
||||
/// </summary>
|
||||
string GetNameById(long id, bool onlyCache = true);
|
||||
string GetNameById(long id);
|
||||
/// <summary>
|
||||
/// 导入
|
||||
/// </summary>
|
||||
|
@@ -18,13 +18,14 @@ using Microsoft.AspNetCore.Components.Forms;
|
||||
using MiniExcelLibs;
|
||||
using MiniExcelLibs.OpenXml;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
using ThingsGateway.Core;
|
||||
using ThingsGateway.Core.Extension;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
@@ -66,11 +67,11 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long GetIdByName(string name, bool onlyCache = false)
|
||||
public long GetIdByName(string name)
|
||||
{
|
||||
//先从Cache拿
|
||||
var id = _sysCacheService.Get<long>(ThingsGatewayCacheConst.Cache_DeviceVariableName, name);
|
||||
if (id == 0 && !onlyCache)
|
||||
if (id == 0)
|
||||
{
|
||||
//单查获取对应ID
|
||||
id = Context.Queryable<DeviceVariable>().Where(it => it.Name == name).Select(it => it.Id).First();
|
||||
@@ -83,11 +84,11 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
return id;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public string GetNameById(long Id, bool onlyCache = true)
|
||||
public string GetNameById(long Id)
|
||||
{
|
||||
//先从Cache拿
|
||||
var name = _sysCacheService.Get<string>(ThingsGatewayCacheConst.Cache_DeviceVariableId, Id.ToString());
|
||||
if (name.IsNullOrEmpty() && !onlyCache)
|
||||
if (name.IsNullOrEmpty())
|
||||
{
|
||||
//单查获取用户账号对应ID
|
||||
name = Context.Queryable<DeviceVariable>().Where(it => it.Id == Id).Select(it => it.Name).First();
|
||||
@@ -287,41 +288,77 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
|
||||
/// <inheritdoc/>
|
||||
[OperDesc("导出变量表", IsRecordPar = false)]
|
||||
public async Task<MemoryStream> MemoryVariableExportFileAsync(List<MemoryVariable> devDatas = null)
|
||||
public async Task<MemoryStream> MemoryVariableExportFileAsync(List<MemoryVariable> deviceVariables = null)
|
||||
{
|
||||
devDatas ??= (await GetListAsync(a => a.IsMemoryVariable == true)).Adapt<List<MemoryVariable>>();
|
||||
deviceVariables ??= (await GetListAsync(a => a.IsMemoryVariable == true)).Adapt<List<MemoryVariable>>();
|
||||
|
||||
//总数据
|
||||
Dictionary<string, object> sheets = new Dictionary<string, object>();
|
||||
//变量页
|
||||
List<Dictionary<string, object>> devExports = new();
|
||||
ConcurrentList<Dictionary<string, object>> devExports = new();
|
||||
//变量附加属性,转成Dict<表名,List<Dict<列名,列数据>>>的形式
|
||||
Dictionary<string, List<Dictionary<string, object>>> devicePropertys = new();
|
||||
await devDatas.ForeachAsync(devData =>
|
||||
ConcurrentDictionary<string, List<Dictionary<string, object>>> devicePropertys = new();
|
||||
var upDeviceDicts = _uploadDeviceService.GetCacheList().ToDictionary(a => a.Id);
|
||||
var collectDeviceDicts = _collectDeviceService.GetCacheList().ToDictionary(a => a.Id);
|
||||
var driverPluginDicts = _driverPluginService.GetCacheList().ToDictionary(a => a.Id);
|
||||
deviceVariables.ParallelForEach(devData =>
|
||||
{
|
||||
#region 变量sheet
|
||||
//变量页
|
||||
var data = devData.GetType().GetAllProps().Where(a => a.GetCustomAttribute<ExcelAttribute>() != null);
|
||||
Dictionary<string, object> devExport = new();
|
||||
Dictionary<string, object> variableExport = new();
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
//描述
|
||||
var desc = ObjectExtensions.FindDisplayAttribute(item);
|
||||
//数据源增加
|
||||
devExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString());
|
||||
variableExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString());
|
||||
}
|
||||
|
||||
|
||||
//添加完整变量信息
|
||||
devExports.Add(devExport);
|
||||
devExports.Add(variableExport);
|
||||
|
||||
#endregion
|
||||
#region 上传插件属性
|
||||
foreach (var item in devData.VariablePropertys ?? new())
|
||||
{
|
||||
//插件属性
|
||||
//单个设备的行数据
|
||||
Dictionary<string, object> driverInfo = new Dictionary<string, object>();
|
||||
var has = upDeviceDicts.TryGetValue(item.Key, out var uploadDevice);
|
||||
if (!has)
|
||||
continue;
|
||||
driverInfo.Add(ExportHelpers.UploadDeviceSheetName, uploadDevice?.Name);
|
||||
//没有包含变量名称,手动插入
|
||||
driverInfo.Add(ExportHelpers.DeviceVariableSheetName, devData.Name);
|
||||
foreach (var item1 in item.Value)
|
||||
{
|
||||
//添加对应属性数据
|
||||
driverInfo.Add(item1.Description, item1.Value);
|
||||
}
|
||||
|
||||
if (uploadDevice != null)
|
||||
{
|
||||
//插件名称去除首部ThingsGateway.作为表名
|
||||
var pluginName = driverPluginDicts[uploadDevice.PluginId].AssembleName.Replace(ExportHelpers.PluginLeftName, "");
|
||||
if (devicePropertys.ContainsKey(pluginName))
|
||||
{
|
||||
devicePropertys[pluginName].Add(driverInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
devicePropertys.TryAdd(pluginName, new() { driverInfo });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
//添加设备页
|
||||
//添加变量页
|
||||
sheets.Add(ExportHelpers.DeviceVariableSheetName, devExports);
|
||||
//添加插件属性页
|
||||
foreach (var item in devicePropertys)
|
||||
@@ -336,76 +373,79 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
|
||||
/// <inheritdoc/>
|
||||
[OperDesc("导出变量表", IsRecordPar = false)]
|
||||
public async Task<MemoryStream> ExportFileAsync(List<DeviceVariable> devDatas = null)
|
||||
public async Task<MemoryStream> ExportFileAsync(List<DeviceVariable> deviceVariables = null)
|
||||
{
|
||||
devDatas ??= await GetListAsync(a => a.IsMemoryVariable != true || a.IsMemoryVariable == null);
|
||||
deviceVariables ??= await GetListAsync(a => a.IsMemoryVariable != true || a.IsMemoryVariable == null);
|
||||
|
||||
//总数据
|
||||
Dictionary<string, object> sheets = new Dictionary<string, object>();
|
||||
//变量页
|
||||
List<Dictionary<string, object>> devExports = new();
|
||||
ConcurrentList<Dictionary<string, object>> devExports = new();
|
||||
//变量附加属性,转成Dict<表名,List<Dict<列名,列数据>>>的形式
|
||||
Dictionary<string, List<Dictionary<string, object>>> devicePropertys = new();
|
||||
await devDatas.ForeachAsync(devData =>
|
||||
{
|
||||
#region 变量sheet
|
||||
//变量页
|
||||
var data = devData.GetType().GetAllProps().Where(a => a.GetCustomAttribute<ExcelAttribute>() != null);
|
||||
Dictionary<string, object> devExport = new();
|
||||
//变量实体没有包含设备名称,手动插入
|
||||
devExport.Add(ExportHelpers.DeviceName, _collectDeviceService.GetNameById(devData.DeviceId));
|
||||
ConcurrentDictionary<string, List<Dictionary<string, object>>> devicePropertys = new();
|
||||
var upDeviceDicts = _uploadDeviceService.GetCacheList().ToDictionary(a => a.Id);
|
||||
var collectDeviceDicts = _collectDeviceService.GetCacheList().ToDictionary(a => a.Id);
|
||||
var driverPluginDicts = _driverPluginService.GetCacheList().ToDictionary(a => a.Id);
|
||||
deviceVariables.ParallelForEach(devData =>
|
||||
{
|
||||
#region 变量sheet
|
||||
//变量页
|
||||
var data = devData.GetType().GetAllProps().Where(a => a.GetCustomAttribute<ExcelAttribute>() != null);
|
||||
Dictionary<string, object> variableExport = new();
|
||||
//变量实体没有包含设备名称,手动插入
|
||||
variableExport.Add(ExportHelpers.DeviceName, collectDeviceDicts[devData.DeviceId]);
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
//描述
|
||||
var desc = ObjectExtensions.FindDisplayAttribute(item);
|
||||
//数据源增加
|
||||
devExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString());
|
||||
}
|
||||
foreach (var item in data)
|
||||
{
|
||||
//描述
|
||||
var desc = ObjectExtensions.FindDisplayAttribute(item);
|
||||
//数据源增加
|
||||
variableExport.Add(desc ?? item.Name, item.GetValue(devData)?.ToString());
|
||||
}
|
||||
|
||||
//添加完整变量信息
|
||||
devExports.Add(variableExport);
|
||||
|
||||
#endregion
|
||||
#region 上传插件属性
|
||||
foreach (var item in devData.VariablePropertys ?? new())
|
||||
{
|
||||
//插件属性
|
||||
//单个设备的行数据
|
||||
Dictionary<string, object> driverInfo = new Dictionary<string, object>();
|
||||
var has = upDeviceDicts.TryGetValue(item.Key, out var uploadDevice);
|
||||
if (!has)
|
||||
continue;
|
||||
driverInfo.Add(ExportHelpers.UploadDeviceSheetName, uploadDevice?.Name);
|
||||
//没有包含变量名称,手动插入
|
||||
driverInfo.Add(ExportHelpers.DeviceVariableSheetName, devData.Name);
|
||||
foreach (var item1 in item.Value)
|
||||
{
|
||||
//添加对应属性数据
|
||||
driverInfo.Add(item1.Description, item1.Value);
|
||||
}
|
||||
|
||||
if (uploadDevice != null)
|
||||
{
|
||||
//插件名称去除首部ThingsGateway.作为表名
|
||||
var pluginName = driverPluginDicts[uploadDevice.PluginId].AssembleName.Replace(ExportHelpers.PluginLeftName, "");
|
||||
if (devicePropertys.ContainsKey(pluginName))
|
||||
{
|
||||
devicePropertys[pluginName].Add(driverInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
devicePropertys.TryAdd(pluginName, new() { driverInfo });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//添加完整变量信息
|
||||
devExports.Add(devExport);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region 上传插件属性
|
||||
foreach (var item in devData.VariablePropertys ?? new())
|
||||
{
|
||||
//插件属性
|
||||
//单个设备的行数据
|
||||
Dictionary<string, object> driverInfo = new Dictionary<string, object>();
|
||||
var uploadDevice = _uploadDeviceService.GetDeviceById(item.Key);
|
||||
driverInfo.Add(ExportHelpers.UploadDeviceSheetName, uploadDevice?.Name);
|
||||
//没有包含变量名称,手动插入
|
||||
driverInfo.Add(ExportHelpers.DeviceVariableSheetName, devData.Name);
|
||||
foreach (var item1 in item.Value)
|
||||
{
|
||||
//添加对应属性数据
|
||||
driverInfo.Add(item1.Description, item1.Value);
|
||||
}
|
||||
});
|
||||
|
||||
if (uploadDevice != null)
|
||||
{
|
||||
//插件名称去除首部ThingsGateway.作为表名
|
||||
var pluginName = _driverPluginService.GetNameById(uploadDevice.PluginId).Replace(ExportHelpers.PluginLeftName, "");
|
||||
if (devicePropertys.ContainsKey(pluginName))
|
||||
{
|
||||
devicePropertys[pluginName].Add(driverInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
devicePropertys.Add(pluginName, new() { driverInfo });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
//添加设备页
|
||||
//添加变量页
|
||||
sheets.Add(ExportHelpers.DeviceVariableSheetName, devExports);
|
||||
//添加插件属性页
|
||||
foreach (var item in devicePropertys)
|
||||
@@ -427,13 +467,14 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
await stream.CopyToAsync(fs);
|
||||
var sheetNames = MiniExcel.GetSheetNames(fs);
|
||||
|
||||
var deviceVariables = await GetListAsync();
|
||||
foreach (var item in deviceVariables)
|
||||
var dbVariables = await Context.Queryable<DeviceVariable>().Select(it => new { it.Id, it.Name }).ToListAsync();
|
||||
foreach (var item in dbVariables)
|
||||
{
|
||||
_sysCacheService.Set(ThingsGatewayCacheConst.Cache_DeviceVariableName, item.Name, item.Id);
|
||||
_sysCacheService.Set(ThingsGatewayCacheConst.Cache_DeviceVariableId, item.Id.ToString(), item.Name);
|
||||
}
|
||||
|
||||
//转为字典,提高查找效率
|
||||
var dbVariableDicts = dbVariables.ToDictionary(a => a.Name);
|
||||
//导入检验结果
|
||||
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
|
||||
//设备页
|
||||
@@ -451,125 +492,35 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
ImportPreviews.Add(sheetName, importPreviewOutput);
|
||||
deviceImportPreview = importPreviewOutput;
|
||||
|
||||
//线程安全
|
||||
List<DeviceVariable> variables = new List<DeviceVariable>();
|
||||
rows.ParallelForEach(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var variable = ((ExpandoObject)item).ConvertToEntity<DeviceVariable>(true);
|
||||
|
||||
List<DeviceVariable> devices = new List<DeviceVariable>();
|
||||
await rows.ForeachAsync(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = ((ExpandoObject)item).ConvertToEntity<DeviceVariable>(true);
|
||||
//var hasDup = rows.HasDuplicateElements<DeviceVariable>(nameof(DeviceVariable.Name), device.Name);
|
||||
//var hasName = GetIdByName(device.Name) > 0;
|
||||
//if (hasDup || hasName)
|
||||
//{
|
||||
// importPreviewOutput.HasError = true;
|
||||
// importPreviewOutput.Results.Add((false, "名称重复"));
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
//变量ID都需要手动补录
|
||||
variables.Add(variable);
|
||||
variable.Id = dbVariableDicts.TryGetValue(variable.Name, out var dbvar1) ? dbvar1.Id : YitIdHelper.NextId();
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, "成功"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, ex.Message));
|
||||
}
|
||||
});
|
||||
|
||||
//var hasDup = rows.HasDuplicateElements<DeviceVariable>(nameof(DeviceVariable.Name), device.Name);
|
||||
//var hasName = GetIdByName(device.Name) > 0;
|
||||
//if (hasDup || hasName)
|
||||
//{
|
||||
// importPreviewOutput.HasError = true;
|
||||
// importPreviewOutput.Results.Add((false, "名称重复"));
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
//变量ID都需要手动补录
|
||||
devices.Add(device);
|
||||
device.Id = this.GetIdByName(device.Name, true) == 0 ? YitIdHelper.NextId() : this.GetIdByName(device.Name, true);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
|
||||
importPreviewOutput.Data = devices;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return ImportPreviews;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file)
|
||||
{
|
||||
_fileService.ImportVerification(file);
|
||||
using var fs = new MemoryStream();
|
||||
using var stream = file.OpenReadStream(512000000);
|
||||
await stream.CopyToAsync(fs);
|
||||
var sheetNames = MiniExcel.GetSheetNames(fs);
|
||||
|
||||
var deviceVariables = await GetListAsync();
|
||||
foreach (var item in deviceVariables)
|
||||
{
|
||||
_sysCacheService.Set(ThingsGatewayCacheConst.Cache_DeviceVariableName, item.Name, item.Id);
|
||||
_sysCacheService.Set(ThingsGatewayCacheConst.Cache_DeviceVariableId, item.Id.ToString(), item.Name);
|
||||
}
|
||||
|
||||
//导入检验结果
|
||||
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
|
||||
//设备页
|
||||
ImportPreviewOutput<DeviceVariable> deviceImportPreview = new();
|
||||
foreach (var sheetName in sheetNames)
|
||||
{
|
||||
//单页数据
|
||||
var rows = fs.Query(useHeaderRow: true, sheetName: sheetName, configuration: new OpenXmlConfiguration { EnableSharedStringCache = false })
|
||||
.Cast<IDictionary<string, object>>();
|
||||
|
||||
if (sheetName == ExportHelpers.DeviceVariableSheetName)
|
||||
{
|
||||
int row = 1;
|
||||
ImportPreviewOutput<DeviceVariable> importPreviewOutput = new();
|
||||
ImportPreviews.Add(sheetName, importPreviewOutput);
|
||||
deviceImportPreview = importPreviewOutput;
|
||||
|
||||
|
||||
List<DeviceVariable> devices = new List<DeviceVariable>();
|
||||
await rows.ForeachAsync(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = ((ExpandoObject)item).ConvertToEntity<DeviceVariable>(true);
|
||||
|
||||
//var hasDup = rows.HasDuplicateElements<DeviceVariable>(nameof(DeviceVariable.Name), device.Name);
|
||||
//var hasName = GetIdByName(device.Name) > 0;
|
||||
//if (hasDup || hasName)
|
||||
//{
|
||||
// importPreviewOutput.HasError = true;
|
||||
// importPreviewOutput.Results.Add((false, "名称重复"));
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
//转化设备名称
|
||||
var deviceName = item.FirstOrDefault(a => a.Key == ExportHelpers.DeviceName).Value;
|
||||
if (_collectDeviceService.GetIdByName(deviceName?.ToString()) == null)
|
||||
{
|
||||
//找不到对应的设备
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, $"{deviceName}设备不存在"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
//变量ID和设备ID都需要手动补录
|
||||
device.DeviceId = _collectDeviceService.GetIdByName(deviceName.ToString()).ToLong();
|
||||
device.Id = this.GetIdByName(device.Name, true) == 0 ? YitIdHelper.NextId() : this.GetIdByName(device.Name, true);
|
||||
}
|
||||
|
||||
devices.Add(device);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
|
||||
importPreviewOutput.Data = devices;
|
||||
importPreviewOutput.Data = variables.OrderBy(a => a.Id).ToDictionary(a => a.Name);
|
||||
|
||||
}
|
||||
else
|
||||
@@ -587,7 +538,7 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
if (pluginId == null)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, $"插件{newName}不存在"));
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, $"插件{newName}不存在"));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -595,59 +546,255 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
using var serviceScope = _scopeFactory.CreateScope();
|
||||
var pluginSingletonService = serviceScope.ServiceProvider.GetService<PluginSingletonService>();
|
||||
var driver = (UpLoadBase)pluginSingletonService.GetDriver(YitIdHelper.NextId(), driverPlugin);
|
||||
var propertys = driver.VariablePropertys.GetType().GetAllProps().Where(a => a.GetCustomAttribute<VariablePropertyAttribute>() != null);
|
||||
await rows.ForeachAsync(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var propertys = driver.VariablePropertys.GetType().GetAllProps()
|
||||
.Where(a => a.GetCustomAttribute<VariablePropertyAttribute>() != null)
|
||||
.ToDictionary(a => a.FindDisplayAttribute(a => a.GetCustomAttribute<VariablePropertyAttribute>()?.Description));
|
||||
|
||||
List<DependencyProperty> devices = new List<DependencyProperty>();
|
||||
foreach (var item1 in item)
|
||||
var cacheUpdeviceDicts = _uploadDeviceService.GetCacheList().ToDictionary(a => a.Name);
|
||||
rows.ParallelForEach(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
List<DependencyProperty> dependencyProperties = new List<DependencyProperty>();
|
||||
foreach (var keyValuePair in item)
|
||||
{
|
||||
if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo))
|
||||
{
|
||||
var propertyInfo = propertys.FirstOrDefault(p => p.FindDisplayAttribute(a => a.GetCustomAttribute<VariablePropertyAttribute>()?.Description) == item1.Key);
|
||||
if (propertyInfo == null)
|
||||
dependencyProperties.Add(new()
|
||||
{
|
||||
//不存在时不报错
|
||||
PropertyName = propertyInfo.Name,
|
||||
Description = keyValuePair.Key.ToString(),
|
||||
Value = keyValuePair.Value?.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
//转化插件名称
|
||||
item.TryGetValue(ExportHelpers.DeviceVariableSheetName, out var variableNameObj);
|
||||
item.TryGetValue(ExportHelpers.UploadDeviceSheetName, out var uploadDevName);
|
||||
var variableName = variableNameObj?.ToString();
|
||||
|
||||
if (uploadDevName != null)
|
||||
{
|
||||
cacheUpdeviceDicts.TryGetValue(uploadDevName.ToString(), out var uploadDevice);
|
||||
|
||||
var has = deviceImportPreview.Data.TryGetValue(variableName, out var deviceVariable);
|
||||
if (has)
|
||||
{
|
||||
if (uploadDevice != null)
|
||||
{
|
||||
deviceVariable.VariablePropertys?.AddOrUpdate(uploadDevice.Id, a => dependencyProperties, (a, b) => dependencyProperties);
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, "成功"));
|
||||
}
|
||||
else
|
||||
{
|
||||
devices.Add(new()
|
||||
{
|
||||
PropertyName = propertyInfo.Name,
|
||||
Description = item1.Key.ToString(),
|
||||
Value = item1.Value?.ToString()
|
||||
});
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, $"上传设备{uploadDevName}不存在"));
|
||||
}
|
||||
|
||||
}
|
||||
//转化插件名称
|
||||
var variableName = item.FirstOrDefault(a => a.Key == ExportHelpers.DeviceVariableSheetName).Value?.ToString();
|
||||
var uploadDevName = item.FirstOrDefault(a => a.Key == ExportHelpers.UploadDeviceSheetName).Value?.ToString();
|
||||
|
||||
var uploadDevice = _uploadDeviceService.GetCacheList().FirstOrDefault(a => a.Name == uploadDevName);
|
||||
|
||||
if (deviceImportPreview.Data?.Any(it => it.Name == variableName) == true && uploadDevice != null)
|
||||
else
|
||||
{
|
||||
var id = this.GetIdByName(variableName, true);
|
||||
var deviceId = id != 0 ? id : deviceImportPreview.Data.FirstOrDefault(it => it.Name == variableName).Id;
|
||||
deviceImportPreview?.Data?.FirstOrDefault(a => a.Id == deviceId)?.VariablePropertys?.AddOrUpdate(uploadDevice.Id, devices);
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, $"上传设备{uploadDevName}不存在"));
|
||||
}
|
||||
importPreviewOutput.Data.Add(string.Empty);
|
||||
importPreviewOutput.Results.Add((row++, true, "成功"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
return Task.CompletedTask;
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, $"上传设备{uploadDevName}不存在"));
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, ex.Message));
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((row++, false, ex.Message));
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return ImportPreviews;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile file)
|
||||
{
|
||||
_fileService.ImportVerification(file);
|
||||
using var fs = new MemoryStream();
|
||||
using var stream = file.OpenReadStream(512000000);
|
||||
await stream.CopyToAsync(fs);
|
||||
var sheetNames = MiniExcel.GetSheetNames(fs);
|
||||
|
||||
var dbVariables = await Context.Queryable<DeviceVariable>().Select(it => new { it.Id, it.Name }).ToListAsync();
|
||||
foreach (var item in dbVariables)
|
||||
{
|
||||
_sysCacheService.Set(ThingsGatewayCacheConst.Cache_DeviceVariableName, item.Name, item.Id);
|
||||
_sysCacheService.Set(ThingsGatewayCacheConst.Cache_DeviceVariableId, item.Id.ToString(), item.Name);
|
||||
}
|
||||
//转为字典,提高查找效率
|
||||
var dbVariableDicts = dbVariables.ToDictionary(a => a.Name);
|
||||
//检验结果
|
||||
Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();
|
||||
//设备页
|
||||
ImportPreviewOutput<DeviceVariable> deviceImportPreview = new();
|
||||
foreach (var sheetName in sheetNames)
|
||||
{
|
||||
//单页数据
|
||||
var rows = fs.Query(useHeaderRow: true, sheetName: sheetName, configuration: new OpenXmlConfiguration { EnableSharedStringCache = false })
|
||||
.Cast<IDictionary<string, object>>();
|
||||
|
||||
if (sheetName == ExportHelpers.DeviceVariableSheetName)
|
||||
{
|
||||
int row = 0;
|
||||
ImportPreviewOutput<DeviceVariable> importPreviewOutput = new();
|
||||
ImportPreviews.Add(sheetName, importPreviewOutput);
|
||||
deviceImportPreview = importPreviewOutput;
|
||||
var cacheDeviceDicts = _collectDeviceService.GetCacheList().ToDictionary(a => a.Name);
|
||||
//线程安全
|
||||
var variables = new ConcurrentList<DeviceVariable>();
|
||||
//并行注意线程安全
|
||||
rows.ParallelForEach(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var variable = ((ExpandoObject)item).ConvertToEntity<DeviceVariable>(true);
|
||||
|
||||
//var hasDup = rows.HasDuplicateElements<DeviceVariable>(nameof(DeviceVariable.Name), device.Name);
|
||||
//var hasName = GetIdByName(device.Name) > 0;
|
||||
//if (hasDup || hasName)
|
||||
//{
|
||||
// importPreviewOutput.HasError = true;
|
||||
// importPreviewOutput.Results.Add((false, "名称重复"));
|
||||
// return Task.CompletedTask;
|
||||
//}
|
||||
//转化设备名称
|
||||
item.TryGetValue(ExportHelpers.DeviceName, out var value);
|
||||
var deviceName = value?.ToString();
|
||||
cacheDeviceDicts.TryGetValue(deviceName, out var device);
|
||||
var deviceId = device?.Id;
|
||||
if (deviceId == null)
|
||||
{
|
||||
//找不到对应的设备
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, $"{deviceName}设备不存在"));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//变量ID和设备ID都需要手动补录
|
||||
variable.DeviceId = deviceId.Value;
|
||||
variable.Id = dbVariableDicts.TryGetValue(variable.Name, out var dbvar1) ? dbvar1.Id : YitIdHelper.NextId();
|
||||
}
|
||||
|
||||
variables.Add(variable);
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, "成功"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, ex.Message));
|
||||
}
|
||||
});
|
||||
|
||||
importPreviewOutput.Data = variables.OrderBy(a => a.Id).ToDictionary(a => a.Name);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int row = 1;
|
||||
ImportPreviewOutput<string> importPreviewOutput = new();
|
||||
ImportPreviews.Add(sheetName, importPreviewOutput);
|
||||
|
||||
//插件属性需加上前置名称
|
||||
var newName = ExportHelpers.PluginLeftName + sheetName;
|
||||
var pluginId = _driverPluginService.GetIdByName(newName);
|
||||
|
||||
try
|
||||
{
|
||||
if (pluginId == null)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, $"插件{newName}不存在"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var driverPlugin = _driverPluginService.GetDriverPluginById(pluginId.Value);
|
||||
using var serviceScope = _scopeFactory.CreateScope();
|
||||
var pluginSingletonService = serviceScope.ServiceProvider.GetService<PluginSingletonService>();
|
||||
var driver = (UpLoadBase)pluginSingletonService.GetDriver(YitIdHelper.NextId(), driverPlugin);
|
||||
var propertys = driver.VariablePropertys.GetType().GetAllProps()
|
||||
.Where(a => a.GetCustomAttribute<VariablePropertyAttribute>() != null)
|
||||
.ToDictionary(a => a.FindDisplayAttribute(a => a.GetCustomAttribute<VariablePropertyAttribute>()?.Description));
|
||||
|
||||
var cacheUpdeviceDicts = _uploadDeviceService.GetCacheList().ToDictionary(a => a.Name);
|
||||
rows.ParallelForEach(item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
List<DependencyProperty> dependencyProperties = new List<DependencyProperty>();
|
||||
foreach (var keyValuePair in item)
|
||||
{
|
||||
if (propertys.TryGetValue(keyValuePair.Key, out var propertyInfo))
|
||||
{
|
||||
dependencyProperties.Add(new()
|
||||
{
|
||||
PropertyName = propertyInfo.Name,
|
||||
Description = keyValuePair.Key.ToString(),
|
||||
Value = keyValuePair.Value?.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
//转化插件名称
|
||||
item.TryGetValue(ExportHelpers.DeviceVariableSheetName, out var variableNameObj);
|
||||
item.TryGetValue(ExportHelpers.UploadDeviceSheetName, out var uploadDevName);
|
||||
var variableName = variableNameObj?.ToString();
|
||||
|
||||
if (uploadDevName != null)
|
||||
{
|
||||
cacheUpdeviceDicts.TryGetValue(uploadDevName.ToString(), out var uploadDevice);
|
||||
|
||||
var has = deviceImportPreview.Data.TryGetValue(variableName, out var deviceVariable);
|
||||
if (has)
|
||||
{
|
||||
if (uploadDevice != null)
|
||||
{
|
||||
deviceVariable.VariablePropertys?.AddOrUpdate(uploadDevice.Id, a => dependencyProperties, (a, b) => dependencyProperties);
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, "成功"));
|
||||
}
|
||||
else
|
||||
{
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, $"上传设备{uploadDevName}不存在"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, $"上传设备{uploadDevName}不存在"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), true, $"上传设备{uploadDevName}不存在"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, ex.Message));
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importPreviewOutput.HasError = true;
|
||||
importPreviewOutput.Results.Add((Interlocked.Add(ref row, 1), false, ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -667,28 +814,16 @@ public class VariableService : DbRepository<DeviceVariable>, IVariableService
|
||||
if (item.Key == ExportHelpers.DeviceVariableSheetName)
|
||||
{
|
||||
var collectDeviceImports = ((ImportPreviewOutput<DeviceVariable>)item.Value).Data;
|
||||
collectDevices = collectDeviceImports.Adapt<List<DeviceVariable>>();
|
||||
collectDevices = collectDeviceImports.Values.ToList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Context.CurrentConnectionConfig.DbType == DbType.Sqlite
|
||||
|| Context.CurrentConnectionConfig.DbType == DbType.SqlServer
|
||||
|| Context.CurrentConnectionConfig.DbType == DbType.MySql
|
||||
|| Context.CurrentConnectionConfig.DbType == DbType.PostgreSQL
|
||||
)
|
||||
await Context.Utilities.PageEachAsync(collectDevices, 10000, async pageList =>
|
||||
{
|
||||
//大量数据插入/更新
|
||||
var x = await Context.Storageable(collectDevices).ToStorageAsync();
|
||||
await x.BulkCopyAsync();//不存在插入
|
||||
await x.BulkUpdateAsync();//存在更新
|
||||
}
|
||||
else
|
||||
{
|
||||
//其他数据库使用普通插入/更新
|
||||
await Context.Storageable(collectDevices).ExecuteCommandAsync();
|
||||
}
|
||||
await Context.Storageable(pageList).ExecuteCommandAsync();
|
||||
});
|
||||
DeleteVariableFromCache();
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -532,10 +532,10 @@ public class AlarmWorker : BackgroundService
|
||||
_logger.LogWarning(ex, "写入历史报警失败");
|
||||
|
||||
var cacheDatas = hisalarm.ChunkTrivialBetter(500);
|
||||
await cacheDatas.ForeachAsync(async a =>
|
||||
foreach (var a in cacheDatas)
|
||||
{
|
||||
await CacheDb.AddCacheData("", a.ToJson(), 50000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -356,6 +356,8 @@ public class CollectDeviceCore : DisposableObject
|
||||
{
|
||||
foreach (var deviceVariableSourceRead in DeviceVariableSourceReads)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
|
||||
if (Device?.KeepRun == false)
|
||||
{
|
||||
continue;
|
||||
@@ -381,11 +383,11 @@ public class CollectDeviceCore : DisposableObject
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
foreach (var deviceVariableMedRead in DeviceVariableMedReads)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
if (Device?.KeepRun == false)
|
||||
continue;
|
||||
if (StoppingToken.IsCancellationRequested)
|
||||
@@ -738,8 +740,7 @@ public class CollectDeviceCore : DisposableObject
|
||||
{
|
||||
_pluginService.DeleteDriver(DeviceId, Device.PluginId);
|
||||
}
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -84,6 +84,7 @@ public class CollectDeviceThread : IDisposable
|
||||
{
|
||||
await device.BeforeActionAsync(StoppingToken.Token);
|
||||
}
|
||||
await Task.Delay(100, StoppingToken.Token);
|
||||
}
|
||||
while (!CollectDeviceCores.All(a => a.IsExited))
|
||||
{
|
||||
|
@@ -93,6 +93,8 @@ public class CollectDeviceWorker : BackgroundService
|
||||
StartAllDeviceThreads();
|
||||
_logger.LogInformation("启动其他服务线程");
|
||||
StartOtherHostService();
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -229,6 +231,7 @@ public class CollectDeviceWorker : BackgroundService
|
||||
deviceCollectCore.Init(collectDeviceRunTime);
|
||||
|
||||
DeviceThread(deviceCollectCore);
|
||||
await Task.Delay(10);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -483,51 +486,61 @@ public class CollectDeviceWorker : BackgroundService
|
||||
await RestartDeviceThreadAsync();
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
//这里不采用CancellationToken控制子线程,直接循环保持,结束时调用子设备线程Dispose
|
||||
//检测设备采集线程假死
|
||||
var num = CollectDeviceCores.Count;
|
||||
for (int i = 0; i < num; i++)
|
||||
try
|
||||
{
|
||||
CollectDeviceCore devcore = CollectDeviceCores[i];
|
||||
if (devcore.Device != null)
|
||||
|
||||
|
||||
//这里不采用CancellationToken控制子线程,直接循环保持,结束时调用子设备线程Dispose
|
||||
//检测设备采集线程假死
|
||||
var num = CollectDeviceCores.Count;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
|
||||
if (
|
||||
(devcore.Device.ActiveTime != DateTime.MinValue
|
||||
&& devcore.Device.ActiveTime.AddMinutes(3) <= DateTime.UtcNow)
|
||||
|| devcore.IsInitSuccess == false
|
||||
)
|
||||
CollectDeviceCore devcore = CollectDeviceCores[i];
|
||||
if (devcore.Device != null)
|
||||
{
|
||||
if (devcore.StoppingTokens.LastOrDefault()?.Token.IsCancellationRequested == true)
|
||||
continue;
|
||||
if (devcore.Device.DeviceStatus == DeviceStatusEnum.Pause)
|
||||
continue;
|
||||
if (!devcore.IsInitSuccess)
|
||||
_logger?.LogWarning(devcore.Device.Name + "初始化失败,重启线程中");
|
||||
else
|
||||
_logger?.LogWarning(devcore.Device.Name + "采集线程假死,重启线程中");
|
||||
await UpDeviceThreadAsync(devcore.DeviceId, false);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogTrace(devcore.Device.Name + "线程检测正常");
|
||||
}
|
||||
|
||||
|
||||
if (devcore.Device.DeviceStatus == DeviceStatusEnum.OffLine)
|
||||
{
|
||||
if (devcore.Device.IsRedundant && _collectDeviceService.GetCacheList().Any(a => a.Id == devcore.Device.RedundantDeviceId))
|
||||
if (
|
||||
(devcore.Device.ActiveTime != DateTime.MinValue
|
||||
&& devcore.Device.ActiveTime.AddMinutes(3) <= DateTime.UtcNow)
|
||||
|| devcore.IsInitSuccess == false
|
||||
)
|
||||
{
|
||||
await UpDeviceRedundantThreadAsync(devcore.Device.Id);
|
||||
if (devcore.StoppingTokens.LastOrDefault()?.Token.IsCancellationRequested == true)
|
||||
continue;
|
||||
if (devcore.Device.DeviceStatus == DeviceStatusEnum.Pause)
|
||||
continue;
|
||||
if (!devcore.IsInitSuccess)
|
||||
_logger?.LogWarning(devcore.Device.Name + "初始化失败,重启线程中");
|
||||
else
|
||||
_logger?.LogWarning(devcore.Device.Name + "采集线程假死,重启线程中");
|
||||
await UpDeviceThreadAsync(devcore.DeviceId, false);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogTrace(devcore.Device.Name + "线程检测正常");
|
||||
}
|
||||
|
||||
|
||||
if (devcore.Device.DeviceStatus == DeviceStatusEnum.OffLine)
|
||||
{
|
||||
if (devcore.Device.IsRedundant && _collectDeviceService.GetCacheList().Any(a => a.Id == devcore.Device.RedundantDeviceId))
|
||||
{
|
||||
await UpDeviceRedundantThreadAsync(devcore.Device.Id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
await Task.Delay(300000, stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ToString());
|
||||
}
|
||||
|
||||
await Task.Delay(300000, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,6 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
using ThingsGateway.Core.Extension;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Enumerator;
|
||||
@@ -245,11 +244,10 @@ public class HistoryValueWorker : BackgroundService
|
||||
if (LastIsSuccess)
|
||||
_logger.LogWarning(ex, "写入历史数据失败");
|
||||
var cacheDatas = collecthis.ChunkTrivialBetter(500);
|
||||
await cacheDatas.ForeachAsync(async a =>
|
||||
{
|
||||
await CacheDb.AddCacheData("", a.ToJson(), 50000);
|
||||
});
|
||||
|
||||
foreach (var a in cacheDatas)
|
||||
{
|
||||
await CacheDb.AddCacheData("", a.ToJson(), 50000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,10 +271,10 @@ public class HistoryValueWorker : BackgroundService
|
||||
if (LastIsSuccess)
|
||||
_logger.LogWarning(ex, "写入历史数据失败");
|
||||
var cacheDatas = changehis.ChunkTrivialBetter(500);
|
||||
await cacheDatas.ForeachAsync(async a =>
|
||||
foreach (var a in cacheDatas)
|
||||
{
|
||||
await CacheDb.AddCacheData("", a.ToJson(), 50000);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -69,6 +69,7 @@ public class UploadDeviceThread : IDisposable
|
||||
{
|
||||
device.Logger?.LogError(ex, "报文日志添加失败");
|
||||
}
|
||||
await Task.Delay(100, StoppingToken.Token);
|
||||
await device.BeforeActionAsync(StoppingToken.Token);
|
||||
}
|
||||
while (!UploadDeviceCores.All(a => a.IsExited))
|
||||
|
@@ -354,39 +354,48 @@ public class UploadDeviceWorker : BackgroundService
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
//这里不采用CancellationToken控制子线程,直接循环保持,结束时调用子设备线程Dispose
|
||||
//检测设备上传线程假死
|
||||
var num = UploadDeviceCores.Count;
|
||||
for (int i = 0; i < num; i++)
|
||||
try
|
||||
{
|
||||
UploadDeviceCore devcore = UploadDeviceCores[i];
|
||||
if (devcore.Device != null)
|
||||
|
||||
|
||||
//这里不采用CancellationToken控制子线程,直接循环保持,结束时调用子设备线程Dispose
|
||||
//检测设备上传线程假死
|
||||
var num = UploadDeviceCores.Count;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
if (
|
||||
(devcore.Device.ActiveTime != DateTime.MinValue
|
||||
&& devcore.Device.ActiveTime.AddMinutes(3) <= DateTime.UtcNow)
|
||||
|| devcore.IsInitSuccess == false
|
||||
)
|
||||
UploadDeviceCore devcore = UploadDeviceCores[i];
|
||||
if (devcore.Device != null)
|
||||
{
|
||||
if (devcore.StoppingTokens.LastOrDefault()?.Token.IsCancellationRequested == true)
|
||||
continue;
|
||||
if (devcore.Device.DeviceStatus == DeviceStatusEnum.Pause)
|
||||
continue;
|
||||
if (!devcore.IsInitSuccess)
|
||||
_logger?.LogWarning(devcore.Device.Name + "初始化失败,重启线程中");
|
||||
if (
|
||||
(devcore.Device.ActiveTime != DateTime.MinValue
|
||||
&& devcore.Device.ActiveTime.AddMinutes(3) <= DateTime.UtcNow)
|
||||
|| devcore.IsInitSuccess == false
|
||||
)
|
||||
{
|
||||
if (devcore.StoppingTokens.LastOrDefault()?.Token.IsCancellationRequested == true)
|
||||
continue;
|
||||
if (devcore.Device.DeviceStatus == DeviceStatusEnum.Pause)
|
||||
continue;
|
||||
if (!devcore.IsInitSuccess)
|
||||
_logger?.LogWarning(devcore.Device.Name + "初始化失败,重启线程中");
|
||||
else
|
||||
_logger?.LogWarning(devcore.Device.Name + "上传线程假死,重启线程中");
|
||||
await UpDeviceThreadAsync(devcore.DeviceId);
|
||||
|
||||
}
|
||||
else
|
||||
_logger?.LogWarning(devcore.Device.Name + "上传线程假死,重启线程中");
|
||||
await UpDeviceThreadAsync(devcore.DeviceId);
|
||||
{
|
||||
_logger?.LogTrace(devcore.Device.Name + "线程检测正常");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogTrace(devcore.Device.Name + "线程检测正常");
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(300000, stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ToString());
|
||||
}
|
||||
await Task.Delay(300000, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -185,12 +185,16 @@ namespace ThingsGateway.Web.Page
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_collectDeviceCores?.FirstOrDefault()?.Device == null)
|
||||
{
|
||||
_collectDeviceGroups = _globalDeviceData.CollectDevices.Adapt<List<CollectDevice>>()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList() ?? new();
|
||||
_collectDeviceCores = CollectDeviceHostService?.CollectDeviceCores?.WhereIf(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device?.DeviceGroup == _collectDeviceGroup).ToList() ?? new();
|
||||
}
|
||||
if (_collectDeviceCores?.FirstOrDefault()?.Device == null || CollectDeviceHostService?.CollectDeviceCores.Count != _collectDeviceCores.Count)
|
||||
{
|
||||
collectDeviceQuery();
|
||||
}
|
||||
|
||||
if (_uploadDeviceCores?.FirstOrDefault()?.Device == null)
|
||||
if (_uploadDeviceCores?.FirstOrDefault()?.Device == null || UploadDeviceHostService?.UploadDeviceCores.Count != _uploadDeviceCores.Count)
|
||||
{
|
||||
uploadDeviceQuery();
|
||||
}
|
||||
|
@@ -488,7 +488,7 @@
|
||||
var data=GetDriverProperties(UploadDevices.FirstOrDefault(a=>a.Id==choiceUploadDeviceId).PluginId,context.VariablePropertys.ContainsKey(choiceUploadDeviceId)?context.VariablePropertys[choiceUploadDeviceId]:null);
|
||||
if(data?.Count>0)
|
||||
{
|
||||
context.VariablePropertys.AddOrUpdate(choiceUploadDeviceId, data);
|
||||
context.VariablePropertys.AddOrUpdate(choiceUploadDeviceId,a=> data,(a,b)=>data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -48,6 +48,7 @@ namespace ThingsGateway.Web.Page
|
||||
{
|
||||
isImport = true;
|
||||
StateHasChanged();
|
||||
ImportPreviews.Clear();
|
||||
ImportPreviews = await Preview.Invoke(file);
|
||||
Step = 2;
|
||||
}
|
||||
|
@@ -396,7 +396,7 @@
|
||||
var data=GetDriverProperties(UploadDevices.FirstOrDefault(a=>a.Id==choiceUploadDeviceId).PluginId,context.VariablePropertys.ContainsKey(choiceUploadDeviceId)?context.VariablePropertys[choiceUploadDeviceId]:null);
|
||||
if(data?.Count>0)
|
||||
{
|
||||
context.VariablePropertys.AddOrUpdate(choiceUploadDeviceId, data);
|
||||
context.VariablePropertys.AddOrUpdate(choiceUploadDeviceId,a=> data,(a,b)=>data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -26,7 +26,8 @@ public class Program
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
|
||||
|
||||
ThreadPool.SetMinThreads(2000, 2000);
|
||||
ThreadPool.SetMaxThreads(1000000, 1000000);
|
||||
|
||||
#if KINGVIEW //读取组态王不能后台启动,所以这里多出来一个解决方案配置
|
||||
SevicesExtension.KINGVIEWCONFIG();
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;KINGVIEW</Configurations>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||
<!--<ServerGarbageCollection>false</ServerGarbageCollection>-->
|
||||
<!--切换为工作站GC策略-->
|
||||
</PropertyGroup>
|
||||
|
||||
|
Reference in New Issue
Block a user