Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
27e8653a1a | ||
![]() |
863beda82c | ||
![]() |
bac84c3ecd | ||
![]() |
2fca2ad9f8 | ||
![]() |
dd75286fe0 | ||
![]() |
7f91792cf1 | ||
![]() |
0e0ccad311 | ||
![]() |
0691f72e67 | ||
![]() |
7e38a51720 | ||
![]() |
34ca8243a3 | ||
![]() |
112fea7632 |
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>3.0.0.10</Version>
|
||||
<Version>3.0.0.13</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<Authors>Diego</Authors>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>3.0.0.10</Version>
|
||||
<Version>3.0.0.13</Version>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFrameworks>net45;netstandard2.0;net6.0;net7.0</TargetFrameworks>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#region copyright
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
@@ -70,6 +70,11 @@ public class OPCNode
|
||||
/// </summary>
|
||||
[Description("安全策略")]
|
||||
public bool IsUseSecurity { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 加载服务端数据类型
|
||||
/// </summary>
|
||||
[Description("加载服务端数据类型")]
|
||||
public bool LoadType { get; set; } = true;
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#region copyright
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
@@ -200,7 +200,7 @@ public class OPCUAClient : IDisposable
|
||||
/// <summary>
|
||||
/// 新增订阅,需要指定订阅组名称,订阅的tag名数组
|
||||
/// </summary>
|
||||
public async Task AddSubscriptionAsync(string subscriptionName, string[] items)
|
||||
public async Task AddSubscriptionAsync(string subscriptionName, string[] items, bool loadType = true)
|
||||
{
|
||||
Subscription m_subscription = new(m_session.DefaultSubscription)
|
||||
{
|
||||
@@ -213,14 +213,14 @@ public class OPCUAClient : IDisposable
|
||||
DisplayName = subscriptionName
|
||||
};
|
||||
List<MonitoredItem> monitoredItems = new();
|
||||
var variableNodes = loadType ? await ReadNodesAsync(items) : null;
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var variableNode = await ReadNodeAsync(items[i], false);
|
||||
var item = new MonitoredItem
|
||||
{
|
||||
StartNodeId = variableNode.NodeId,
|
||||
StartNodeId = loadType ? variableNodes[i].NodeId : items[i],
|
||||
AttributeId = Attributes.Value,
|
||||
DisplayName = items[i],
|
||||
Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
|
||||
@@ -307,11 +307,11 @@ public class OPCUAClient : IDisposable
|
||||
|
||||
}
|
||||
|
||||
private async void Callback(MonitoredItem monitoreditem, MonitoredItemNotificationEventArgs monitoredItemNotificationEventArgs)
|
||||
private void Callback(MonitoredItem monitoreditem, MonitoredItemNotificationEventArgs monitoredItemNotificationEventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
var variableNode = await ReadNodeAsync(monitoreditem.StartNodeId.ToString(), false);
|
||||
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false);
|
||||
foreach (var value in monitoreditem.DequeueValues())
|
||||
{
|
||||
if (value.Value != null)
|
||||
@@ -532,7 +532,7 @@ public class OPCUAClient : IDisposable
|
||||
|
||||
//如果是订阅模式,连接时添加订阅组
|
||||
if (OPCNode.ActiveSubscribe)
|
||||
await AddSubscriptionAsync(Guid.NewGuid().ToString(), Variables.ToArray());
|
||||
await AddSubscriptionAsync(Guid.NewGuid().ToString(), Variables.ToArray(), OPCNode.LoadType);
|
||||
return m_session;
|
||||
}
|
||||
|
||||
@@ -680,634 +680,67 @@ public class OPCUAClient : IDisposable
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
NodeId nodeToRead = new(nodeIdStr);
|
||||
var node = (VariableNode)await ReadNodeAsync(nodeToRead, NodeClass.Unspecified, false, cancellationToken);
|
||||
await typeSystem.LoadType(node.DataType, true, false);
|
||||
var node = (VariableNode)await m_session.ReadNodeAsync(nodeToRead, NodeClass.Variable, false, cancellationToken);
|
||||
if (OPCNode.LoadType)
|
||||
await typeSystem.LoadType(node.DataType);
|
||||
_variableDicts.AddOrUpdate(nodeIdStr, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从服务器或缓存读取节点
|
||||
/// </summary>
|
||||
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true)
|
||||
{
|
||||
if (!isOnlyServer)
|
||||
{
|
||||
if (_variableDicts.TryGetValue(nodeIdStr, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
NodeId nodeToRead = new(nodeIdStr);
|
||||
var node = (VariableNode)m_session.ReadNode(nodeToRead, NodeClass.Variable, false);
|
||||
_variableDicts.AddOrUpdate(nodeIdStr, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region session
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Node> ReadNodeAsync(
|
||||
NodeId nodeId,
|
||||
NodeClass nodeClass,
|
||||
bool optionalAttributes = true,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// build list of attributes.
|
||||
var attributes = CreateAttributes(nodeClass, optionalAttributes);
|
||||
|
||||
// build list of values to read.
|
||||
ReadValueIdCollection itemsToRead = new ReadValueIdCollection();
|
||||
foreach (uint attributeId in attributes.Keys)
|
||||
{
|
||||
ReadValueId itemToRead = new ReadValueId
|
||||
{
|
||||
NodeId = nodeId,
|
||||
AttributeId = attributeId
|
||||
};
|
||||
itemsToRead.Add(itemToRead);
|
||||
}
|
||||
|
||||
// read from server.
|
||||
ReadResponse readResponse = await m_session.ReadAsync(
|
||||
null,
|
||||
0,
|
||||
TimestampsToReturn.Neither,
|
||||
itemsToRead, ct).ConfigureAwait(false);
|
||||
|
||||
DataValueCollection values = readResponse.Results;
|
||||
DiagnosticInfoCollection diagnosticInfos = readResponse.DiagnosticInfos;
|
||||
|
||||
ClientBase.ValidateResponse(values, itemsToRead);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead);
|
||||
|
||||
return ProcessReadResponse(readResponse.ResponseHeader, attributes, itemsToRead, values, diagnosticInfos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Node based on the read response.
|
||||
/// 从服务器读取节点
|
||||
/// </summary>
|
||||
private Node ProcessReadResponse(
|
||||
ResponseHeader responseHeader,
|
||||
IDictionary<uint, DataValue> attributes,
|
||||
ReadValueIdCollection itemsToRead,
|
||||
DataValueCollection values,
|
||||
DiagnosticInfoCollection diagnosticInfos)
|
||||
private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// process results.
|
||||
int? nodeClass = null;
|
||||
|
||||
for (int ii = 0; ii < itemsToRead.Count; ii++)
|
||||
List<NodeId> nodeIds = new List<NodeId>();
|
||||
foreach (var item in nodeIdStrs)
|
||||
{
|
||||
uint attributeId = itemsToRead[ii].AttributeId;
|
||||
|
||||
// the node probably does not exist if the node class is not found.
|
||||
if (attributeId == Attributes.NodeClass)
|
||||
NodeId nodeToRead = new(item);
|
||||
nodeIds.Add(nodeToRead);
|
||||
}
|
||||
(IList<Node>, IList<ServiceResult>) nodes = await m_session.ReadNodesAsync(nodeIds, NodeClass.Variable, false, cancellationToken);
|
||||
for (int i = 0; i < nodes.Item1.Count; i++)
|
||||
{
|
||||
if (StatusCode.IsGood(nodes.Item2[i].StatusCode))
|
||||
{
|
||||
if (!DataValue.IsGood(values[ii]))
|
||||
{
|
||||
throw ServiceResultException.Create(values[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable);
|
||||
}
|
||||
|
||||
// check for valid node class.
|
||||
nodeClass = values[ii].Value as int?;
|
||||
|
||||
if (nodeClass == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", values[ii].Value);
|
||||
}
|
||||
var node = ((VariableNode)nodes.Item1[i]);
|
||||
await typeSystem.LoadType(node.DataType);
|
||||
_variableDicts.AddOrUpdate(nodeIdStrs[i], node);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!DataValue.IsGood(values[ii]))
|
||||
{
|
||||
// check for unsupported attributes.
|
||||
if (values[ii].StatusCode == StatusCodes.BadAttributeIdInvalid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore errors on optional attributes
|
||||
if (StatusCode.IsBad(values[ii].StatusCode))
|
||||
{
|
||||
if (attributeId == Attributes.AccessRestrictions ||
|
||||
attributeId == Attributes.Description ||
|
||||
attributeId == Attributes.RolePermissions ||
|
||||
attributeId == Attributes.UserRolePermissions ||
|
||||
attributeId == Attributes.DataTypeDefinition ||
|
||||
attributeId == Attributes.AccessLevelEx ||
|
||||
attributeId == Attributes.UserWriteMask ||
|
||||
attributeId == Attributes.WriteMask)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// all supported attributes must be readable.
|
||||
if (attributeId != Attributes.Value)
|
||||
{
|
||||
throw ServiceResultException.Create(values[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attributes[attributeId] = values[ii];
|
||||
}
|
||||
|
||||
Node node;
|
||||
DataValue value;
|
||||
switch ((NodeClass)nodeClass.Value)
|
||||
{
|
||||
default:
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", nodeClass.Value);
|
||||
}
|
||||
|
||||
case NodeClass.Object:
|
||||
{
|
||||
ObjectNode objectNode = new ObjectNode();
|
||||
|
||||
value = attributes[Attributes.EventNotifier];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Object does not support the EventNotifier attribute.");
|
||||
}
|
||||
|
||||
objectNode.EventNotifier = (byte)value.GetValue(typeof(byte));
|
||||
node = objectNode;
|
||||
break;
|
||||
}
|
||||
|
||||
case NodeClass.ObjectType:
|
||||
{
|
||||
ObjectTypeNode objectTypeNode = new ObjectTypeNode();
|
||||
|
||||
value = attributes[Attributes.IsAbstract];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ObjectType does not support the IsAbstract attribute.");
|
||||
}
|
||||
|
||||
objectTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool));
|
||||
node = objectTypeNode;
|
||||
break;
|
||||
}
|
||||
|
||||
case NodeClass.Variable:
|
||||
{
|
||||
VariableNode variableNode = new VariableNode();
|
||||
|
||||
// DataType Attribute
|
||||
value = attributes[Attributes.DataType];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the DataType attribute.");
|
||||
}
|
||||
|
||||
variableNode.DataType = (NodeId)value.GetValue(typeof(NodeId));
|
||||
|
||||
// ValueRank Attribute
|
||||
value = attributes[Attributes.ValueRank];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the ValueRank attribute.");
|
||||
}
|
||||
|
||||
variableNode.ValueRank = (int)value.GetValue(typeof(int));
|
||||
|
||||
// ArrayDimensions Attribute
|
||||
value = attributes[Attributes.ArrayDimensions];
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
if (value.Value == null)
|
||||
{
|
||||
variableNode.ArrayDimensions = Array.Empty<uint>();
|
||||
}
|
||||
else
|
||||
{
|
||||
variableNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[]));
|
||||
}
|
||||
}
|
||||
|
||||
// AccessLevel Attribute
|
||||
value = attributes[Attributes.AccessLevel];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the AccessLevel attribute.");
|
||||
}
|
||||
|
||||
variableNode.AccessLevel = (byte)value.GetValue(typeof(byte));
|
||||
|
||||
// UserAccessLevel Attribute
|
||||
value = attributes[Attributes.UserAccessLevel];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the UserAccessLevel attribute.");
|
||||
}
|
||||
|
||||
variableNode.UserAccessLevel = (byte)value.GetValue(typeof(byte));
|
||||
|
||||
// Historizing Attribute
|
||||
value = attributes[Attributes.Historizing];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the Historizing attribute.");
|
||||
}
|
||||
|
||||
variableNode.Historizing = (bool)value.GetValue(typeof(bool));
|
||||
|
||||
// MinimumSamplingInterval Attribute
|
||||
value = attributes[Attributes.MinimumSamplingInterval];
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
variableNode.MinimumSamplingInterval = Convert.ToDouble(attributes[Attributes.MinimumSamplingInterval].Value);
|
||||
}
|
||||
|
||||
// AccessLevelEx Attribute
|
||||
value = attributes[Attributes.AccessLevelEx];
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
variableNode.AccessLevelEx = (uint)value.GetValue(typeof(uint));
|
||||
}
|
||||
|
||||
node = variableNode;
|
||||
break;
|
||||
}
|
||||
|
||||
case NodeClass.VariableType:
|
||||
{
|
||||
VariableTypeNode variableTypeNode = new VariableTypeNode();
|
||||
|
||||
// IsAbstract Attribute
|
||||
value = attributes[Attributes.IsAbstract];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the IsAbstract attribute.");
|
||||
}
|
||||
|
||||
variableTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool));
|
||||
|
||||
// DataType Attribute
|
||||
value = attributes[Attributes.DataType];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the DataType attribute.");
|
||||
}
|
||||
|
||||
variableTypeNode.DataType = (NodeId)value.GetValue(typeof(NodeId));
|
||||
|
||||
// ValueRank Attribute
|
||||
value = attributes[Attributes.ValueRank];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the ValueRank attribute.");
|
||||
}
|
||||
|
||||
variableTypeNode.ValueRank = (int)value.GetValue(typeof(int));
|
||||
|
||||
// ArrayDimensions Attribute
|
||||
value = attributes[Attributes.ArrayDimensions];
|
||||
|
||||
if (value != null && value.Value != null)
|
||||
{
|
||||
variableTypeNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[]));
|
||||
}
|
||||
|
||||
node = variableTypeNode;
|
||||
break;
|
||||
}
|
||||
|
||||
case NodeClass.Method:
|
||||
{
|
||||
MethodNode methodNode = new MethodNode();
|
||||
|
||||
// Executable Attribute
|
||||
value = attributes[Attributes.Executable];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the Executable attribute.");
|
||||
}
|
||||
|
||||
methodNode.Executable = (bool)value.GetValue(typeof(bool));
|
||||
|
||||
// UserExecutable Attribute
|
||||
value = attributes[Attributes.UserExecutable];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the UserExecutable attribute.");
|
||||
}
|
||||
|
||||
methodNode.UserExecutable = (bool)value.GetValue(typeof(bool));
|
||||
|
||||
node = methodNode;
|
||||
break;
|
||||
}
|
||||
|
||||
case NodeClass.DataType:
|
||||
{
|
||||
DataTypeNode dataTypeNode = new DataTypeNode();
|
||||
|
||||
// IsAbstract Attribute
|
||||
value = attributes[Attributes.IsAbstract];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "DataType does not support the IsAbstract attribute.");
|
||||
}
|
||||
|
||||
dataTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool));
|
||||
|
||||
// DataTypeDefinition Attribute
|
||||
value = attributes[Attributes.DataTypeDefinition];
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
dataTypeNode.DataTypeDefinition = value.Value as ExtensionObject;
|
||||
}
|
||||
|
||||
node = dataTypeNode;
|
||||
break;
|
||||
}
|
||||
|
||||
case NodeClass.ReferenceType:
|
||||
{
|
||||
ReferenceTypeNode referenceTypeNode = new ReferenceTypeNode();
|
||||
|
||||
// IsAbstract Attribute
|
||||
value = attributes[Attributes.IsAbstract];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the IsAbstract attribute.");
|
||||
}
|
||||
|
||||
referenceTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool));
|
||||
|
||||
// Symmetric Attribute
|
||||
value = attributes[Attributes.Symmetric];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the Symmetric attribute.");
|
||||
}
|
||||
|
||||
referenceTypeNode.Symmetric = (bool)value.GetValue(typeof(bool));
|
||||
|
||||
// InverseName Attribute
|
||||
value = attributes[Attributes.InverseName];
|
||||
|
||||
if (value != null && value.Value != null)
|
||||
{
|
||||
referenceTypeNode.InverseName = (LocalizedText)value.GetValue(typeof(LocalizedText));
|
||||
}
|
||||
|
||||
node = referenceTypeNode;
|
||||
break;
|
||||
}
|
||||
|
||||
case NodeClass.View:
|
||||
{
|
||||
ViewNode viewNode = new ViewNode();
|
||||
|
||||
// EventNotifier Attribute
|
||||
value = attributes[Attributes.EventNotifier];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the EventNotifier attribute.");
|
||||
}
|
||||
|
||||
viewNode.EventNotifier = (byte)value.GetValue(typeof(byte));
|
||||
|
||||
// ContainsNoLoops Attribute
|
||||
value = attributes[Attributes.ContainsNoLoops];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the ContainsNoLoops attribute.");
|
||||
}
|
||||
|
||||
viewNode.ContainsNoLoops = (bool)value.GetValue(typeof(bool));
|
||||
|
||||
node = viewNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NodeId Attribute
|
||||
value = attributes[Attributes.NodeId];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the NodeId attribute.");
|
||||
}
|
||||
|
||||
node.NodeId = (NodeId)value.GetValue(typeof(NodeId));
|
||||
node.NodeClass = (NodeClass)nodeClass.Value;
|
||||
|
||||
// BrowseName Attribute
|
||||
value = attributes[Attributes.BrowseName];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the BrowseName attribute.");
|
||||
}
|
||||
|
||||
node.BrowseName = (QualifiedName)value.GetValue(typeof(QualifiedName));
|
||||
|
||||
// DisplayName Attribute
|
||||
value = attributes[Attributes.DisplayName];
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the DisplayName attribute.");
|
||||
}
|
||||
|
||||
node.DisplayName = (LocalizedText)value.GetValue(typeof(LocalizedText));
|
||||
|
||||
// all optional attributes follow
|
||||
|
||||
// Description Attribute
|
||||
if (attributes.TryGetValue(Attributes.Description, out value) &&
|
||||
value != null && value.Value != null)
|
||||
{
|
||||
node.Description = (LocalizedText)value.GetValue(typeof(LocalizedText));
|
||||
}
|
||||
|
||||
// WriteMask Attribute
|
||||
if (attributes.TryGetValue(Attributes.WriteMask, out value) &&
|
||||
value != null)
|
||||
{
|
||||
node.WriteMask = (uint)value.GetValue(typeof(uint));
|
||||
}
|
||||
|
||||
// UserWriteMask Attribute
|
||||
if (attributes.TryGetValue(Attributes.UserWriteMask, out value) &&
|
||||
value != null)
|
||||
{
|
||||
node.UserWriteMask = (uint)value.GetValue(typeof(uint));
|
||||
}
|
||||
|
||||
// RolePermissions Attribute
|
||||
if (attributes.TryGetValue(Attributes.RolePermissions, out value) &&
|
||||
value != null)
|
||||
{
|
||||
ExtensionObject[] rolePermissions = value.Value as ExtensionObject[];
|
||||
|
||||
if (rolePermissions != null)
|
||||
{
|
||||
node.RolePermissions = new RolePermissionTypeCollection();
|
||||
|
||||
foreach (ExtensionObject rolePermission in rolePermissions)
|
||||
{
|
||||
node.RolePermissions.Add(rolePermission.Body as RolePermissionType);
|
||||
}
|
||||
_logAction?.Invoke(3, this, $"获取服务器节点信息失败{nodes.Item2[i]}", null);
|
||||
}
|
||||
}
|
||||
|
||||
// UserRolePermissions Attribute
|
||||
if (attributes.TryGetValue(Attributes.UserRolePermissions, out value) &&
|
||||
value != null)
|
||||
{
|
||||
ExtensionObject[] userRolePermissions = value.Value as ExtensionObject[];
|
||||
|
||||
if (userRolePermissions != null)
|
||||
{
|
||||
node.UserRolePermissions = new RolePermissionTypeCollection();
|
||||
|
||||
foreach (ExtensionObject rolePermission in userRolePermissions)
|
||||
{
|
||||
node.UserRolePermissions.Add(rolePermission.Body as RolePermissionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AccessRestrictions Attribute
|
||||
if (attributes.TryGetValue(Attributes.AccessRestrictions, out value) &&
|
||||
value != null)
|
||||
{
|
||||
node.AccessRestrictions = (ushort)value.GetValue(typeof(ushort));
|
||||
}
|
||||
|
||||
return node;
|
||||
return nodes.Item1.ToList();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a dictionary of attributes to read for a nodeclass.
|
||||
/// </summary>
|
||||
private IDictionary<uint, DataValue> CreateAttributes(NodeClass nodeclass = NodeClass.Unspecified, bool optionalAttributes = true)
|
||||
{
|
||||
// Attributes to read for all types of nodes
|
||||
var attributes = new SortedDictionary<uint, DataValue>() {
|
||||
{ Attributes.NodeId, null },
|
||||
{ Attributes.NodeClass, null },
|
||||
{ Attributes.BrowseName, null },
|
||||
{ Attributes.DisplayName, null },
|
||||
};
|
||||
|
||||
switch (nodeclass)
|
||||
{
|
||||
case NodeClass.Object:
|
||||
attributes.Add(Attributes.EventNotifier, null);
|
||||
break;
|
||||
|
||||
case NodeClass.Variable:
|
||||
attributes.Add(Attributes.DataType, null);
|
||||
attributes.Add(Attributes.ValueRank, null);
|
||||
attributes.Add(Attributes.ArrayDimensions, null);
|
||||
attributes.Add(Attributes.AccessLevel, null);
|
||||
attributes.Add(Attributes.UserAccessLevel, null);
|
||||
attributes.Add(Attributes.Historizing, null);
|
||||
attributes.Add(Attributes.MinimumSamplingInterval, null);
|
||||
attributes.Add(Attributes.AccessLevelEx, null);
|
||||
break;
|
||||
|
||||
case NodeClass.Method:
|
||||
attributes.Add(Attributes.Executable, null);
|
||||
attributes.Add(Attributes.UserExecutable, null);
|
||||
break;
|
||||
|
||||
case NodeClass.ObjectType:
|
||||
attributes.Add(Attributes.IsAbstract, null);
|
||||
break;
|
||||
|
||||
case NodeClass.VariableType:
|
||||
attributes.Add(Attributes.IsAbstract, null);
|
||||
attributes.Add(Attributes.DataType, null);
|
||||
attributes.Add(Attributes.ValueRank, null);
|
||||
attributes.Add(Attributes.ArrayDimensions, null);
|
||||
break;
|
||||
|
||||
case NodeClass.ReferenceType:
|
||||
attributes.Add(Attributes.IsAbstract, null);
|
||||
attributes.Add(Attributes.Symmetric, null);
|
||||
attributes.Add(Attributes.InverseName, null);
|
||||
break;
|
||||
|
||||
case NodeClass.DataType:
|
||||
attributes.Add(Attributes.IsAbstract, null);
|
||||
attributes.Add(Attributes.DataTypeDefinition, null);
|
||||
break;
|
||||
|
||||
case NodeClass.View:
|
||||
attributes.Add(Attributes.EventNotifier, null);
|
||||
attributes.Add(Attributes.ContainsNoLoops, null);
|
||||
break;
|
||||
|
||||
default:
|
||||
// build complete list of attributes.
|
||||
attributes = new SortedDictionary<uint, DataValue> {
|
||||
{ Attributes.NodeId, null },
|
||||
{ Attributes.NodeClass, null },
|
||||
{ Attributes.BrowseName, null },
|
||||
{ Attributes.DisplayName, null },
|
||||
//{ Attributes.Description, null },
|
||||
//{ Attributes.WriteMask, null },
|
||||
//{ Attributes.UserWriteMask, null },
|
||||
{ Attributes.DataType, null },
|
||||
{ Attributes.ValueRank, null },
|
||||
{ Attributes.ArrayDimensions, null },
|
||||
{ Attributes.AccessLevel, null },
|
||||
{ Attributes.UserAccessLevel, null },
|
||||
{ Attributes.MinimumSamplingInterval, null },
|
||||
{ Attributes.Historizing, null },
|
||||
{ Attributes.EventNotifier, null },
|
||||
{ Attributes.Executable, null },
|
||||
{ Attributes.UserExecutable, null },
|
||||
{ Attributes.IsAbstract, null },
|
||||
{ Attributes.InverseName, null },
|
||||
{ Attributes.Symmetric, null },
|
||||
{ Attributes.ContainsNoLoops, null },
|
||||
{ Attributes.DataTypeDefinition, null },
|
||||
//{ Attributes.RolePermissions, null },
|
||||
//{ Attributes.UserRolePermissions, null },
|
||||
//{ Attributes.AccessRestrictions, null },
|
||||
{ Attributes.AccessLevelEx, null }
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (optionalAttributes)
|
||||
{
|
||||
attributes.Add(Attributes.Description, null);
|
||||
attributes.Add(Attributes.WriteMask, null);
|
||||
attributes.Add(Attributes.UserWriteMask, null);
|
||||
attributes.Add(Attributes.RolePermissions, null);
|
||||
attributes.Add(Attributes.UserRolePermissions, null);
|
||||
attributes.Add(Attributes.AccessRestrictions, null);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region 特性
|
||||
|
||||
/// <summary>
|
||||
|
@@ -498,9 +498,9 @@ namespace ThingsGateway.Foundation.Sockets
|
||||
this.Clear();
|
||||
|
||||
this.m_serverState = ServerState.Stopped;
|
||||
if (this.PluginsManager.Enable)
|
||||
if (this.PluginsManager?.Enable == true)
|
||||
{
|
||||
this.m_pluginsManager.Raise(nameof(IServerStopedPlugin.OnServerStoped), this, new ServiceStateEventArgs(this.m_serverState, default));
|
||||
this.m_pluginsManager?.Raise(nameof(IServerStopedPlugin.OnServerStoped), this, new ServiceStateEventArgs(this.m_serverState, default));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -540,11 +540,11 @@ namespace ThingsGateway.Foundation.Sockets
|
||||
this.Clear();
|
||||
|
||||
this.m_serverState = ServerState.Disposed;
|
||||
if (this.PluginsManager.Enable)
|
||||
if (this.PluginsManager?.Enable == true)
|
||||
{
|
||||
this.m_pluginsManager.Raise(nameof(IServerStopedPlugin.OnServerStoped), this, new ServiceStateEventArgs(this.m_serverState, default));
|
||||
this.m_pluginsManager?.Raise(nameof(IServerStopedPlugin.OnServerStoped), this, new ServiceStateEventArgs(this.m_serverState, default));
|
||||
}
|
||||
this.PluginsManager.SafeDispose();
|
||||
this.PluginsManager?.SafeDispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>3.0.0.10</Version>
|
||||
<Version>3.0.0.13</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
|
@@ -200,6 +200,7 @@ public class OPCUAClient : CollectBase
|
||||
UserName = driverPropertys.UserName,
|
||||
Password = driverPropertys.Password,
|
||||
CheckDomain = driverPropertys.CheckDomain,
|
||||
LoadType = driverPropertys.LoadType,
|
||||
};
|
||||
if (_plc == null)
|
||||
{
|
||||
|
@@ -10,6 +10,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
|
||||
namespace ThingsGateway.Plugin.OPCUA;
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -51,6 +52,12 @@ public class OPCUAClientProperty : CollectDriverPropertyBase
|
||||
[DeviceProperty("使用SourceTime", "")]
|
||||
public bool SourceTimestampEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 加载服务端数据类型
|
||||
/// </summary>
|
||||
[DeviceProperty("加载服务端数据类型")]
|
||||
public bool LoadType { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 激活订阅
|
||||
/// </summary>
|
||||
|
@@ -40,6 +40,7 @@
|
||||
<MCheckbox Class="ma-1" Label=@node.DescriptionWithOutSugar(a=>a.ActiveSubscribe) Dense Outlined HideDetails="@("auto")" @bind-Value=@node.ActiveSubscribe />
|
||||
<MCheckbox Class="ma-1" Label=@node.DescriptionWithOutSugar(a=>a.IsUseSecurity) Dense HideDetails="@("auto")" @bind-Value=@node.IsUseSecurity />
|
||||
<MCheckbox Class="ma-1" Label=@node.DescriptionWithOutSugar(a=>a.CheckDomain) Dense HideDetails="@("auto")" @bind-Value=@node.CheckDomain />
|
||||
<MCheckbox Class="ma-1" Label=@node.DescriptionWithOutSugar(a=>a.LoadType) Dense HideDetails="@("auto")" @bind-Value=@node.LoadType />
|
||||
|
||||
<MButton Class="ma-1" OnClick=@ConnectAsync Color="primary">
|
||||
连接
|
||||
|
@@ -147,6 +147,7 @@ public partial class OPCUAImportVariable
|
||||
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.UserName), Value = PLC.OPCNode.UserName, Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.UserName)).GetCustomAttribute<DevicePropertyAttribute>().Description });
|
||||
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.Password), Value = PLC.OPCNode.Password, Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.Password)).GetCustomAttribute<DevicePropertyAttribute>().Description });
|
||||
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.CheckDomain), Value = PLC.OPCNode.CheckDomain.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.CheckDomain)).GetCustomAttribute<DevicePropertyAttribute>().Description });
|
||||
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.LoadType), Value = PLC.OPCNode.LoadType.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.LoadType)).GetCustomAttribute<DevicePropertyAttribute>().Description });
|
||||
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.IsUseSecurity), Value = PLC.OPCNode.IsUseSecurity.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.IsUseSecurity)).GetCustomAttribute<DevicePropertyAttribute>().Description });
|
||||
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.ActiveSubscribe), Value = true.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.ActiveSubscribe)).GetCustomAttribute<DevicePropertyAttribute>().Description });
|
||||
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.DeadBand), Value = PLC.OPCNode.DeadBand.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.DeadBand)).GetCustomAttribute<DevicePropertyAttribute>().Description });
|
||||
|
@@ -0,0 +1,15 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
global using ThingsGateway.Foundation.Core;
|
||||
global using ThingsGateway.Gateway.Application;
|
||||
global using ThingsGateway.Gateway.Core;
|
325
framework/Plugin/ThingsGateway.Plugin.QuestDB/QuestDB.cs
Normal file
325
framework/Plugin/ThingsGateway.Plugin.QuestDB/QuestDB.cs
Normal file
@@ -0,0 +1,325 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using Furion;
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Plugin.QuestDB;
|
||||
public class QuestDB : UpLoadBase
|
||||
{
|
||||
private readonly ConcurrentQueue<QuestDBHistoryValue> DeviceVariableRunTimes = new();
|
||||
private readonly QuestDBProperty driverPropertys = new();
|
||||
private readonly QuestDBVariableProperty variablePropertys = new();
|
||||
private TypeAdapterConfig _config;
|
||||
private GlobalDeviceData _globalDeviceData;
|
||||
|
||||
private List<DeviceVariableRunTime> _uploadVariables = new();
|
||||
private TimerTick exTimerTick;
|
||||
public QuestDB()
|
||||
{
|
||||
_config = new TypeAdapterConfig();
|
||||
_config.ForType<DeviceVariableRunTime, HistoryValue>()
|
||||
.Map(dest => dest.Value, (src) => ValueReturn(src))
|
||||
.Map(dest => dest.CollectTime, (src) => src.CollectTime.ToUniversalTime())//注意sqlsugar插入时无时区,直接utc时间
|
||||
.Map(dest => dest.CreateTime, (src) => DateTime.UtcNow);//注意sqlsugar插入时无时区,直接utc时间
|
||||
}
|
||||
private static object ValueReturn(DeviceVariableRunTime src)
|
||||
{
|
||||
if (src.Value?.ToString()?.IsBoolValue() == true)
|
||||
{
|
||||
if (src.Value.ToBoolean())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return src.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Type DriverDebugUIType => null;
|
||||
public override UpDriverPropertyBase DriverPropertys => driverPropertys;
|
||||
public override List<DeviceVariableRunTime> UploadVariables => _uploadVariables;
|
||||
|
||||
public override VariablePropertyBase VariablePropertys => variablePropertys;
|
||||
|
||||
public override Task AfterStopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task BeforStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
SqlSugarClient db = GetHisDbAsync();
|
||||
db.DbMaintenance.CreateDatabase();
|
||||
db.CodeFirst.InitTables(typeof(QuestDBHistoryValue));
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var db = GetHisDbAsync();
|
||||
{
|
||||
if (!driverPropertys.IsInterval)
|
||||
{
|
||||
try
|
||||
{
|
||||
////变化推送
|
||||
var varList = DeviceVariableRunTimes.ToListWithDequeue();
|
||||
if (varList?.Count != 0)
|
||||
{
|
||||
await InserableAsync(db, varList, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exTimerTick.IsTickHappen())
|
||||
{
|
||||
try
|
||||
{
|
||||
var varList = _uploadVariables.ToList().Adapt<List<QuestDBHistoryValue>>(_config);
|
||||
if (varList?.Count != 0)
|
||||
{
|
||||
await InserableAsync(db, varList, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (driverPropertys.CycleInterval > UploadDeviceThread.CycleInterval + 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(driverPropertys.CycleInterval - UploadDeviceThread.CycleInterval, cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override bool IsConnected() => _uploadVariables?.Count > 0;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
_globalDeviceData?.AllVariables?.ForEach(a => a.VariableValueChange -= VariableValueChange);
|
||||
_uploadVariables = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogError(ex, ToString());
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void Init(UploadDeviceRunTime device)
|
||||
{
|
||||
|
||||
_globalDeviceData = App.GetService<GlobalDeviceData>();
|
||||
|
||||
var tags = _globalDeviceData.AllVariables.Where(a => a.VariablePropertys.ContainsKey(device.Id))
|
||||
.Where(b => GetPropertyValue(b, nameof(variablePropertys.Enable)).ToBoolean())
|
||||
.ToList();
|
||||
|
||||
_uploadVariables = tags;
|
||||
|
||||
if (!driverPropertys.IsInterval)
|
||||
{
|
||||
_uploadVariables.ForEach(a =>
|
||||
{
|
||||
a.VariableValueChange += VariableValueChange;
|
||||
});
|
||||
}
|
||||
|
||||
if (_uploadVariables.Count == 0)
|
||||
{
|
||||
LogMessage.LogWarning("插件变量数量为0");
|
||||
}
|
||||
if (driverPropertys.IntervalTime < 1)
|
||||
driverPropertys.IntervalTime = 10;
|
||||
exTimerTick = new(driverPropertys.IntervalTime * 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aop设置
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
private static void AopSetting(SqlSugarClient db)
|
||||
{
|
||||
var config = db.CurrentConnectionConfig;
|
||||
|
||||
// 设置超时时间
|
||||
db.Ado.CommandTimeOut = 30;
|
||||
|
||||
// 打印SQL语句
|
||||
db.Aop.OnLogExecuting = (sql, pars) =>
|
||||
{
|
||||
//如果不是开发环境就打印sql
|
||||
if (App.HostEnvironment.IsDevelopment())
|
||||
{
|
||||
if (sql.StartsWith("SELECT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
}
|
||||
if (sql.StartsWith("UPDATE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
}
|
||||
if (sql.StartsWith("INSERT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
}
|
||||
if (sql.StartsWith("DELETE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
}
|
||||
WriteSqlLog(UtilMethods.GetSqlString(config.DbType, sql, pars));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
}
|
||||
};
|
||||
//异常
|
||||
db.Aop.OnError = (ex) =>
|
||||
{
|
||||
//如果不是开发环境就打印日志
|
||||
if (App.WebHostEnvironment.IsDevelopment())
|
||||
{
|
||||
if (ex.Parametres == null) return;
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
|
||||
WriteSqlLogError(UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private static void WriteSqlLog(string msg)
|
||||
{
|
||||
Console.WriteLine("【Sql执行时间】:" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
|
||||
private static void WriteSqlLogError(string msg)
|
||||
{
|
||||
Console.WriteLine("【Sql执行错误时间】:" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库链接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private SqlSugarClient GetHisDbAsync()
|
||||
{
|
||||
var configureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required
|
||||
{
|
||||
if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
|| (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null))
|
||||
column.IsNullable = true;
|
||||
},
|
||||
};
|
||||
var sqlSugarClient = new SqlSugarClient(new ConnectionConfig()
|
||||
{
|
||||
ConnectionString = driverPropertys.ConnectStr,//连接字符串
|
||||
DbType = DbType.QuestDB,//数据库类型
|
||||
IsAutoCloseConnection = true, //不设成true要手动close
|
||||
ConfigureExternalServices = configureExternalServices,
|
||||
}
|
||||
);
|
||||
AopSetting(sqlSugarClient);//aop配置
|
||||
return sqlSugarClient;
|
||||
}
|
||||
|
||||
private async Task InserableAsync(SqlSugarClient db, List<QuestDBHistoryValue> dbInserts, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await db.Insertable(dbInserts).ExecuteCommandAsync(cancellationToken);
|
||||
if (result > 0)
|
||||
LogMessage.Trace(FoundationConst.LogMessageHeader + dbInserts.ToJsonString());
|
||||
//连接成功时补发缓存数据
|
||||
var cacheData = await CacheDb.GetCacheData();
|
||||
foreach (var item in cacheData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = item.CacheStr.FromJsonString<List<QuestDBHistoryValue>>();
|
||||
var cacheresult = await db.Insertable(data).ExecuteCommandAsync(cancellationToken);
|
||||
if (cacheresult > 0)
|
||||
{
|
||||
await CacheDb.DeleteCacheData(item.Id);
|
||||
LogMessage.Trace(FoundationConst.LogMessageHeader + $"主题:{item.Topic}{Environment.NewLine}负载:{item.CacheStr}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogWarning(ex, ToString());
|
||||
await CacheDb.AddCacheData("", dbInserts.ToJsonString(), driverPropertys.CacheMaxCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void VariableValueChange(DeviceVariableRunTime collectVariableRunTime)
|
||||
{
|
||||
if (!driverPropertys.IsInterval)
|
||||
DeviceVariableRunTimes.Enqueue(collectVariableRunTime.Adapt<QuestDBHistoryValue>(_config));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,62 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Gateway.Core;
|
||||
/// <summary>
|
||||
/// 历史数据表
|
||||
/// </summary>
|
||||
[SugarTable("historyValue", TableDescription = "历史数据表")]
|
||||
public class QuestDBHistoryValue
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集时间
|
||||
/// </summary>
|
||||
[TimeDbSplitField(DateType.Month)]
|
||||
[Description("采集时间")]
|
||||
public DateTime CollectTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上传时间
|
||||
/// </summary>
|
||||
[Description("上传时间")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "symbol")]
|
||||
[Description("设备名称")]
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变量名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "symbol")]
|
||||
[Description("变量名称")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否在线
|
||||
/// </summary>
|
||||
[Description("是否在线")]
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变量值
|
||||
/// </summary>
|
||||
[Description("变量值")]
|
||||
public double Value { get; set; }
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Plugin.QuestDB;
|
||||
|
||||
public class QuestDBProperty : UpDriverPropertyBase
|
||||
{
|
||||
[DeviceProperty("链接字符串", "")] public string ConnectStr { get; set; } = "host=localhost;port=8812;username=admin;password=quest;database=qdb;ServerCompatibilityMode=NoTypeLoading;";
|
||||
[DeviceProperty("是否间隔插入", "False时将每次变化写入")] public bool IsInterval { get; set; } = true;
|
||||
[DeviceProperty("间隔时间", "秒,实时表时代表更新间隔,历史表时代表插入间隔")] public int IntervalTime { get; set; } = 10;
|
||||
[DeviceProperty("缓存最大条数", "默认2千条")] public int CacheMaxCount { get; set; } = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// 线程循环间隔
|
||||
/// </summary>
|
||||
[DeviceProperty("线程循环间隔", "最小10ms")]
|
||||
public int CycleInterval { get; set; } = 1000;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,22 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Plugin.QuestDB;
|
||||
|
||||
public class QuestDBVariableProperty : VariablePropertyBase
|
||||
{
|
||||
[VariableProperty("启用", "")]
|
||||
public bool Enable { get; set; } = true;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
||||
<Exec Command=" set dir="$(SolutionDir)Web\ThingsGateway.Web.Entry\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)"
 if not exist %25dir%25 md %25dir%25 
copy "$(TargetDir)*QuestDB*.dll" %25dir%25



" />
|
||||
|
||||
</Target>
|
||||
|
||||
|
||||
|
||||
|
||||
</Project>
|
15
framework/Plugin/ThingsGateway.Plugin.SQLDB/GlobalUsings.cs
Normal file
15
framework/Plugin/ThingsGateway.Plugin.SQLDB/GlobalUsings.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
global using ThingsGateway.Foundation.Core;
|
||||
global using ThingsGateway.Gateway.Application;
|
||||
global using ThingsGateway.Gateway.Core;
|
332
framework/Plugin/ThingsGateway.Plugin.SQLDB/SQLDB.cs
Normal file
332
framework/Plugin/ThingsGateway.Plugin.SQLDB/SQLDB.cs
Normal file
@@ -0,0 +1,332 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using Furion;
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
using Yitter.IdGenerator;
|
||||
|
||||
namespace ThingsGateway.Plugin.SQLDB;
|
||||
public class SQLDB : UpLoadBase
|
||||
{
|
||||
private readonly ConcurrentQueue<SQLHistoryValue> DeviceVariableRunTimes = new();
|
||||
private readonly SQLDBProperty driverPropertys = new();
|
||||
private readonly SQLDBVariableProperty variablePropertys = new();
|
||||
private TypeAdapterConfig _config;
|
||||
private GlobalDeviceData _globalDeviceData;
|
||||
|
||||
private List<DeviceVariableRunTime> _uploadVariables = new();
|
||||
private TimerTick exTimerTick;
|
||||
private TimerTick exRealTimerTick;
|
||||
public SQLDB()
|
||||
{
|
||||
_config = new TypeAdapterConfig();
|
||||
_config.ForType<DeviceVariableRunTime, SQLHistoryValue>()
|
||||
.Map(dest => dest.Id, (src) => YitIdHelper.NextId())
|
||||
.Map(dest => dest.CreateTime, (src) => DateTime.Now);
|
||||
}
|
||||
|
||||
public override Type DriverDebugUIType => null;
|
||||
public override UpDriverPropertyBase DriverPropertys => driverPropertys;
|
||||
public override List<DeviceVariableRunTime> UploadVariables => _uploadVariables;
|
||||
|
||||
public override VariablePropertyBase VariablePropertys => variablePropertys;
|
||||
|
||||
public override Task AfterStopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task BeforStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var db = GetHisDbAsync();
|
||||
db.CodeFirst.InitTables(typeof(SQLHistoryValue));
|
||||
db.MappingTables.Add("SQLRealValue", driverPropertys.ReadDBTableName); // typeof(类).Name 可以拿到类名
|
||||
db.CodeFirst.InitTables(typeof(SQLRealValue)); //生成的表名是 newTableName
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var db = GetHisDbAsync();
|
||||
|
||||
if (driverPropertys.IsReadDB)
|
||||
{
|
||||
if (exRealTimerTick.IsTickHappen())
|
||||
{
|
||||
try
|
||||
{
|
||||
var varList = _uploadVariables.ToList().Adapt<List<SQLRealValue>>();
|
||||
if (varList?.Count != 0)
|
||||
{
|
||||
var result = await db.Storageable(varList).As(driverPropertys.ReadDBTableName).ExecuteCommandAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (!driverPropertys.IsInterval)
|
||||
{
|
||||
try
|
||||
{
|
||||
////变化推送
|
||||
var varList = DeviceVariableRunTimes.ToListWithDequeue();
|
||||
if (varList?.Count != 0)
|
||||
{
|
||||
await InserableAsync(db, varList, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exTimerTick.IsTickHappen())
|
||||
{
|
||||
try
|
||||
{
|
||||
var varList = _uploadVariables.ToList().Adapt<List<SQLHistoryValue>>(_config);
|
||||
if (varList?.Count != 0)
|
||||
{
|
||||
await InserableAsync(db, varList, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (driverPropertys.CycleInterval > UploadDeviceThread.CycleInterval + 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(driverPropertys.CycleInterval - UploadDeviceThread.CycleInterval, cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库链接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public SqlSugarClient GetHisDbAsync()
|
||||
{
|
||||
var configureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required
|
||||
{
|
||||
if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
|| (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null))
|
||||
column.IsNullable = true;
|
||||
},
|
||||
};
|
||||
var sqlSugarClient = new SqlSugarClient(new ConnectionConfig()
|
||||
{
|
||||
ConnectionString = driverPropertys.ConnectStr,//连接字符串
|
||||
DbType = driverPropertys.DbType,//数据库类型
|
||||
IsAutoCloseConnection = true, //不设成true要手动close
|
||||
ConfigureExternalServices = configureExternalServices,
|
||||
}
|
||||
);
|
||||
AopSetting(sqlSugarClient);//aop配置
|
||||
return sqlSugarClient;
|
||||
}
|
||||
|
||||
public override bool IsConnected() => _uploadVariables?.Count > 0;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
_globalDeviceData?.AllVariables?.ForEach(a => a.VariableValueChange -= VariableValueChange);
|
||||
_uploadVariables = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogError(ex, ToString());
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void Init(UploadDeviceRunTime device)
|
||||
{
|
||||
|
||||
_globalDeviceData = App.GetService<GlobalDeviceData>();
|
||||
|
||||
var tags = _globalDeviceData.AllVariables.Where(a => a.VariablePropertys.ContainsKey(device.Id))
|
||||
.Where(b => GetPropertyValue(b, nameof(variablePropertys.Enable)).ToBoolean())
|
||||
.ToList();
|
||||
|
||||
_uploadVariables = tags;
|
||||
|
||||
if (!driverPropertys.IsReadDB)
|
||||
if (!driverPropertys.IsInterval)
|
||||
{
|
||||
_uploadVariables.ForEach(a =>
|
||||
{
|
||||
a.VariableValueChange += VariableValueChange;
|
||||
});
|
||||
}
|
||||
|
||||
if (_uploadVariables.Count == 0)
|
||||
{
|
||||
LogMessage.LogWarning("插件变量数量为0");
|
||||
}
|
||||
if (driverPropertys.IntervalTime < 1)
|
||||
driverPropertys.IntervalTime = 10;
|
||||
exTimerTick = new(driverPropertys.IntervalTime * 1000);
|
||||
exRealTimerTick = new(driverPropertys.IntervalTime * 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aop设置
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
private static void AopSetting(SqlSugarClient db)
|
||||
{
|
||||
var config = db.CurrentConnectionConfig;
|
||||
|
||||
// 设置超时时间
|
||||
db.Ado.CommandTimeOut = 30;
|
||||
|
||||
// 打印SQL语句
|
||||
db.Aop.OnLogExecuting = (sql, pars) =>
|
||||
{
|
||||
//如果不是开发环境就打印sql
|
||||
if (App.HostEnvironment.IsDevelopment())
|
||||
{
|
||||
if (sql.StartsWith("SELECT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
}
|
||||
if (sql.StartsWith("UPDATE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
}
|
||||
if (sql.StartsWith("INSERT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
}
|
||||
if (sql.StartsWith("DELETE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
}
|
||||
WriteSqlLog(UtilMethods.GetSqlString(config.DbType, sql, pars));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
}
|
||||
};
|
||||
//异常
|
||||
db.Aop.OnError = (ex) =>
|
||||
{
|
||||
//如果不是开发环境就打印日志
|
||||
if (App.WebHostEnvironment.IsDevelopment())
|
||||
{
|
||||
if (ex.Parametres == null) return;
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
|
||||
WriteSqlLogError(UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
private static void WriteSqlLog(string msg)
|
||||
{
|
||||
Console.WriteLine("【Sql执行时间】:" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
private static void WriteSqlLogError(string msg)
|
||||
{
|
||||
Console.WriteLine("【Sql执行错误时间】:" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
private async Task InserableAsync(SqlSugarClient db, List<SQLHistoryValue> dbInserts, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync();
|
||||
if (result > 0)
|
||||
LogMessage.Trace(FoundationConst.LogMessageHeader + dbInserts.ToJsonString());
|
||||
//连接成功时补发缓存数据
|
||||
var cacheData = await CacheDb.GetCacheData();
|
||||
foreach (var item in cacheData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = item.CacheStr.FromJsonString<List<SQLHistoryValue>>();
|
||||
var cacheresult = await db.Insertable(data).SplitTable().ExecuteCommandAsync();
|
||||
if (cacheresult > 0)
|
||||
{
|
||||
await CacheDb.DeleteCacheData(item.Id);
|
||||
LogMessage.Trace(FoundationConst.LogMessageHeader + $"主题:{item.Topic}{Environment.NewLine}负载:{item.CacheStr}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogWarning(ex, ToString());
|
||||
await CacheDb.AddCacheData("", dbInserts.ToJsonString(), driverPropertys.CacheMaxCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void VariableValueChange(DeviceVariableRunTime collectVariableRunTime)
|
||||
{
|
||||
if (!driverPropertys.IsReadDB)
|
||||
if (!driverPropertys.IsInterval)
|
||||
DeviceVariableRunTimes.Enqueue(collectVariableRunTime.Adapt<SQLHistoryValue>(_config));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
37
framework/Plugin/ThingsGateway.Plugin.SQLDB/SQLDBProperty.cs
Normal file
37
framework/Plugin/ThingsGateway.Plugin.SQLDB/SQLDBProperty.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
namespace ThingsGateway.Plugin.SQLDB;
|
||||
|
||||
public class SQLDBProperty : UpDriverPropertyBase
|
||||
{
|
||||
[DeviceProperty("是否实时表", "true=>实时表更新,false=>历史存储(按月分表)")] public bool IsReadDB { get; set; } = false;
|
||||
[DeviceProperty("实时表名称", "")] public string ReadDBTableName { get; set; } = "ReadDBTableName";
|
||||
|
||||
[DeviceProperty("数据库类型", "MySql/SqlServer")] public DbType DbType { get; set; } = DbType.MySql;
|
||||
[DeviceProperty("链接字符串", "")] public string ConnectStr { get; set; } = "server=localhost;Database=test;Uid=root;Pwd=111111;";
|
||||
[DeviceProperty("是否间隔插入", "False时将每次变化写入")] public bool IsInterval { get; set; } = true;
|
||||
[DeviceProperty("间隔时间", "秒,实时表时代表更新间隔,历史表时代表插入间隔")] public int IntervalTime { get; set; } = 10;
|
||||
[DeviceProperty("缓存最大条数", "默认2千条")] public int CacheMaxCount { get; set; } = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// 线程循环间隔
|
||||
/// </summary>
|
||||
[DeviceProperty("线程循环间隔", "最小10ms")]
|
||||
public int CycleInterval { get; set; } = 1000;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,22 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Plugin.SQLDB;
|
||||
|
||||
public class SQLDBVariableProperty : VariablePropertyBase
|
||||
{
|
||||
[VariableProperty("启用", "")]
|
||||
public bool Enable { get; set; } = true;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,55 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
namespace ThingsGateway.Plugin.SQLDB;
|
||||
|
||||
[SplitTable(SplitType.Month)]//按月分表 (自带分表支持 年、季、月、周、日)
|
||||
[SugarTable("historyValue_{year}{month}{day}", TableDescription = "设备采集历史表")]//3个变量必须要有
|
||||
[SugarIndex("index_Name", nameof(SQLHistoryValue.Name), OrderByType.Desc)]
|
||||
[SugarIndex("index_DeviceName", nameof(SQLHistoryValue.DeviceName), OrderByType.Desc)]
|
||||
public class SQLHistoryValue
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
|
||||
public long Id { get; set; }
|
||||
/// <summary>
|
||||
/// 变量名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "Name", ColumnDescription = "变量名称")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "DeviceName", ColumnDescription = "设备名称")]
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
///<summary>
|
||||
///实时值
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "Value", ColumnDescription = "实时值")]
|
||||
public string Value { get; set; }
|
||||
|
||||
///<summary>
|
||||
///是否在线
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "IsOnline", ColumnDescription = "是否在线 True=在线;False=离线")]
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
|
||||
public DateTime CollectTime { get; set; }
|
||||
|
||||
[SplitField] //分表字段 在插入的时候会根据这个字段插入哪个表,在更新删除的时候用这个字段找出相关表
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
51
framework/Plugin/ThingsGateway.Plugin.SQLDB/SQLRealValue.cs
Normal file
51
framework/Plugin/ThingsGateway.Plugin.SQLDB/SQLRealValue.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
namespace ThingsGateway.Plugin.SQLDB;
|
||||
|
||||
[SugarTable(TableDescription = "设备采集实时表")]
|
||||
[SugarIndex("index_Name", nameof(SQLHistoryValue.Name), OrderByType.Desc)]
|
||||
[SugarIndex("index_DeviceName", nameof(SQLHistoryValue.DeviceName), OrderByType.Desc)]
|
||||
public class SQLRealValue
|
||||
{
|
||||
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
|
||||
public long Id { get; set; }
|
||||
/// <summary>
|
||||
/// 变量名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "Name", ColumnDescription = "变量名称")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "DeviceName", ColumnDescription = "设备名称")]
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
///<summary>
|
||||
///实时值
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "Value", ColumnDescription = "实时值")]
|
||||
public string Value { get; set; }
|
||||
|
||||
///<summary>
|
||||
///是否在线
|
||||
///</summary>
|
||||
[SugarColumn(ColumnName = "IsOnline", ColumnDescription = "是否在线 True=在线;False=离线")]
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
public DateTime CollectTime { get; set; }
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
||||
<Exec Command=" set dir="$(SolutionDir)Web\ThingsGateway.Web.Entry\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)"
 if not exist %25dir%25 md %25dir%25 
copy "$(TargetDir)*SQLDB*.dll" %25dir%25



" />
|
||||
|
||||
</Target>
|
||||
|
||||
|
||||
|
||||
</Project>
|
@@ -0,0 +1,15 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
global using ThingsGateway.Foundation.Core;
|
||||
global using ThingsGateway.Gateway.Application;
|
||||
global using ThingsGateway.Gateway.Core;
|
@@ -0,0 +1,62 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using SqlSugar;
|
||||
using SqlSugar.TDengine;
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Gateway.Core;
|
||||
/// <summary>
|
||||
/// 历史数据表
|
||||
/// </summary>
|
||||
[SugarTable("historyValue")]
|
||||
public class TDHistoryValue : STable
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传时间
|
||||
/// </summary>
|
||||
[SugarColumn(InsertServerTime = true)]
|
||||
[Description("上传时间")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 采集时间
|
||||
/// </summary>
|
||||
[Description("采集时间")]
|
||||
public DateTime CollectTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[Description("设备名称")]
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变量名称
|
||||
/// </summary>
|
||||
[Description("变量名称")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否在线
|
||||
/// </summary>
|
||||
[Description("是否在线")]
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变量值
|
||||
/// </summary>
|
||||
[Description("变量值")]
|
||||
[SugarColumn(Length = 18, DecimalDigits = 2)]
|
||||
public double Value { get; set; }
|
||||
}
|
325
framework/Plugin/ThingsGateway.Plugin.TDengineDB/TDengineDB.cs
Normal file
325
framework/Plugin/ThingsGateway.Plugin.TDengineDB/TDengineDB.cs
Normal file
@@ -0,0 +1,325 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
|
||||
using Furion;
|
||||
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Plugin.SQLDB;
|
||||
public class TDengineDB : UpLoadBase
|
||||
{
|
||||
private readonly ConcurrentQueue<TDHistoryValue> DeviceVariableRunTimes = new();
|
||||
private readonly TDengineDBProperty driverPropertys = new();
|
||||
private readonly TDengineDBVariableProperty variablePropertys = new();
|
||||
private GlobalDeviceData _globalDeviceData;
|
||||
private TypeAdapterConfig _config;
|
||||
|
||||
private List<DeviceVariableRunTime> _uploadVariables = new();
|
||||
private TimerTick exTimerTick;
|
||||
public TDengineDB()
|
||||
{
|
||||
_config = new TypeAdapterConfig();
|
||||
_config.ForType<DeviceVariableRunTime, HistoryValue>()
|
||||
.Map(dest => dest.Value, (src) => ValueReturn(src));
|
||||
}
|
||||
private static object ValueReturn(DeviceVariableRunTime src)
|
||||
{
|
||||
if (src.Value?.ToString()?.IsBoolValue() == true)
|
||||
{
|
||||
if (src.Value.ToBoolean())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return src.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override Type DriverDebugUIType => null;
|
||||
public override UpDriverPropertyBase DriverPropertys => driverPropertys;
|
||||
public override List<DeviceVariableRunTime> UploadVariables => _uploadVariables;
|
||||
|
||||
public override VariablePropertyBase VariablePropertys => variablePropertys;
|
||||
|
||||
public override Task AfterStopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task BeforStartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
SqlSugarClient db = GetHisDbAsync();
|
||||
db.DbMaintenance.CreateDatabase();
|
||||
db.CodeFirst.InitTables(typeof(TDHistoryValue));
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var db = GetHisDbAsync();
|
||||
{
|
||||
if (!driverPropertys.IsInterval)
|
||||
{
|
||||
try
|
||||
{
|
||||
////变化推送
|
||||
var varList = DeviceVariableRunTimes.ToListWithDequeue();
|
||||
if (varList?.Count != 0)
|
||||
{
|
||||
await InserableAsync(db, varList, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exTimerTick.IsTickHappen())
|
||||
{
|
||||
try
|
||||
{
|
||||
var varList = _uploadVariables.ToList().Adapt<List<TDHistoryValue>>(_config);
|
||||
if (varList?.Count != 0)
|
||||
{
|
||||
await InserableAsync(db, varList, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (driverPropertys.CycleInterval > UploadDeviceThread.CycleInterval + 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(driverPropertys.CycleInterval - UploadDeviceThread.CycleInterval, cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override bool IsConnected() => _uploadVariables?.Count > 0;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
_globalDeviceData?.AllVariables?.ForEach(a => a.VariableValueChange -= VariableValueChange);
|
||||
_uploadVariables = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogError(ex, ToString());
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void Init(UploadDeviceRunTime device)
|
||||
{
|
||||
|
||||
_globalDeviceData = App.GetService<GlobalDeviceData>();
|
||||
|
||||
var tags = _globalDeviceData.AllVariables.Where(a => a.VariablePropertys.ContainsKey(device.Id))
|
||||
.Where(b => GetPropertyValue(b, nameof(variablePropertys.Enable)).ToBoolean())
|
||||
.ToList();
|
||||
|
||||
_uploadVariables = tags;
|
||||
|
||||
if (!driverPropertys.IsInterval)
|
||||
{
|
||||
_uploadVariables.ForEach(a =>
|
||||
{
|
||||
a.VariableValueChange += VariableValueChange;
|
||||
});
|
||||
}
|
||||
|
||||
if (_uploadVariables.Count == 0)
|
||||
{
|
||||
LogMessage.LogWarning("插件变量数量为0");
|
||||
}
|
||||
if (driverPropertys.IntervalTime < 1)
|
||||
driverPropertys.IntervalTime = 10;
|
||||
exTimerTick = new(driverPropertys.IntervalTime * 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aop设置
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
private static void AopSetting(SqlSugarClient db)
|
||||
{
|
||||
var config = db.CurrentConnectionConfig;
|
||||
|
||||
// 设置超时时间
|
||||
db.Ado.CommandTimeOut = 30;
|
||||
|
||||
// 打印SQL语句
|
||||
db.Aop.OnLogExecuting = (sql, pars) =>
|
||||
{
|
||||
//如果不是开发环境就打印sql
|
||||
if (App.HostEnvironment.IsDevelopment())
|
||||
{
|
||||
if (sql.StartsWith("SELECT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
}
|
||||
if (sql.StartsWith("UPDATE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
}
|
||||
if (sql.StartsWith("INSERT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
}
|
||||
if (sql.StartsWith("DELETE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
}
|
||||
WriteSqlLog(UtilMethods.GetSqlString(config.DbType, sql, pars));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
}
|
||||
};
|
||||
//异常
|
||||
db.Aop.OnError = (ex) =>
|
||||
{
|
||||
//如果不是开发环境就打印日志
|
||||
if (App.WebHostEnvironment.IsDevelopment())
|
||||
{
|
||||
if (ex.Parametres == null) return;
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
|
||||
WriteSqlLogError(UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private static void WriteSqlLog(string msg)
|
||||
{
|
||||
Console.WriteLine("【Sql执行时间】:" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
|
||||
private static void WriteSqlLogError(string msg)
|
||||
{
|
||||
Console.WriteLine("【Sql执行错误时间】:" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库链接
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private SqlSugarClient GetHisDbAsync()
|
||||
{
|
||||
var configureExternalServices = new ConfigureExternalServices
|
||||
{
|
||||
EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required
|
||||
{
|
||||
if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
|| (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null))
|
||||
column.IsNullable = true;
|
||||
},
|
||||
};
|
||||
var sqlSugarClient = new SqlSugarClient(new ConnectionConfig()
|
||||
{
|
||||
ConnectionString = driverPropertys.ConnectStr,//连接字符串
|
||||
DbType = driverPropertys.DbType,//数据库类型
|
||||
IsAutoCloseConnection = true, //不设成true要手动close
|
||||
ConfigureExternalServices = configureExternalServices,
|
||||
}
|
||||
);
|
||||
AopSetting(sqlSugarClient);//aop配置
|
||||
return sqlSugarClient;
|
||||
}
|
||||
|
||||
private async Task InserableAsync(SqlSugarClient db, List<TDHistoryValue> dbInserts, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await db.Insertable(dbInserts).ExecuteCommandAsync(cancellationToken);
|
||||
if (result > 0)
|
||||
LogMessage.Trace(FoundationConst.LogMessageHeader + dbInserts.ToJsonString());
|
||||
//连接成功时补发缓存数据
|
||||
var cacheData = await CacheDb.GetCacheData();
|
||||
foreach (var item in cacheData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = item.CacheStr.FromJsonString<List<TDHistoryValue>>();
|
||||
var cacheresult = await db.Insertable(data).ExecuteCommandAsync(cancellationToken);
|
||||
if (cacheresult > 0)
|
||||
{
|
||||
await CacheDb.DeleteCacheData(item.Id);
|
||||
LogMessage.Trace(FoundationConst.LogMessageHeader + $"主题:{item.Topic}{Environment.NewLine}负载:{item.CacheStr}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogWarning(ex, ToString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage.LogWarning(ex, ToString());
|
||||
await CacheDb.AddCacheData("", dbInserts.ToJsonString(), driverPropertys.CacheMaxCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void VariableValueChange(DeviceVariableRunTime collectVariableRunTime)
|
||||
{
|
||||
if (!driverPropertys.IsInterval)
|
||||
DeviceVariableRunTimes.Enqueue(collectVariableRunTime.Adapt<TDHistoryValue>(_config));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,35 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
|
||||
using SqlSugar;
|
||||
|
||||
namespace ThingsGateway.Plugin.SQLDB;
|
||||
|
||||
public class TDengineDBProperty : UpDriverPropertyBase
|
||||
{
|
||||
[DeviceProperty("数据库类型", "QuestDB/TDengine,主要TD需要手动建库,并指定超级表")] public DbType DbType { get; set; } = DbType.TDengine;
|
||||
[DeviceProperty("链接字符串", "")] public string ConnectStr { get; set; } = "Host=localhost;Port=6030;Username=root;Password=taosdata;Database=test";
|
||||
[DeviceProperty("是否间隔插入", "False时将每次变化写入")] public bool IsInterval { get; set; } = true;
|
||||
[DeviceProperty("间隔时间", "秒,实时表时代表更新间隔,历史表时代表插入间隔")] public int IntervalTime { get; set; } = 10;
|
||||
[DeviceProperty("缓存最大条数", "默认2千条")] public int CacheMaxCount { get; set; } = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// 线程循环间隔
|
||||
/// </summary>
|
||||
[DeviceProperty("线程循环间隔", "最小10ms")]
|
||||
public int CycleInterval { get; set; } = 1000;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,22 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Plugin.SQLDB;
|
||||
|
||||
public class TDengineDBVariableProperty : VariablePropertyBase
|
||||
{
|
||||
[VariableProperty("启用", "")]
|
||||
public bool Enable { get; set; } = true;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
||||
<Exec Command=" set dir="$(SolutionDir)Web\ThingsGateway.Web.Entry\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)"
 if not exist %25dir%25 md %25dir%25 
copy "$(TargetDir)*TDengineDB*.dll" %25dir%25



" />
|
||||
|
||||
</Target>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SqlSugar.TDengineCore" Version="2.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
||||
<Exec Command=" set dir="$(SolutionDir)Web\ThingsGateway.Web.Entry\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)"
 if not exist %25dir%25 md %25dir%25 
copy "$(TargetDir)*ThingsGateway.Plugin.TDengineDB*.dll" %25dir%25



" />
|
||||
|
||||
</Target>
|
||||
|
||||
|
||||
|
||||
</Project>
|
@@ -52,10 +52,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Core", "W
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Core", "Web\ThingsGateway.Core\ThingsGateway.Core.csproj", "{51313113-7BB8-494E-9C24-6787BECE39BB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Admin", "Admin", "{79E7042F-F9E3-4D87-BFA9-4B7DD9736735}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "Gateway", "{000C3C62-345E-451C-8CEE-6F2C6A087116}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Gateway.ApiController", "Web\ThingsGateway.Gateway.ApiController\ThingsGateway.Gateway.ApiController.csproj", "{5D7BE567-2345-46C8-9F54-DDC1DA96D198}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Gateway.Application", "Web\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj", "{5CF1B3EC-84E2-484A-8DFC-2ECD2EE18E2F}"
|
||||
@@ -64,8 +60,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Gateway.Blazo
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Gateway.Core", "Web\ThingsGateway.Gateway.Core\ThingsGateway.Gateway.Core.csproj", "{5CD79F91-7182-4A9D-9BEF-4DF410C782D2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{BB9C2A85-7A8A-4CF9-BF44-34DE9848EC15}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugin", "Plugin", "{CC8D0880-B73E-4DFC-9052-86504728708E}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Plugin\Directory.Build.props = Plugin\Directory.Build.props
|
||||
@@ -105,6 +99,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation.De
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation.Demo.Photino", "Demo\ThingsGateway.Foundation.Demo.Photino\ThingsGateway.Foundation.Demo.Photino.csproj", "{C5519C51-0A0C-4317-A43D-FFBB6B344ACB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Plugin.SQLDB", "Plugin\ThingsGateway.Plugin.SQLDB\ThingsGateway.Plugin.SQLDB.csproj", "{7EBD5500-0DA0-415A-831D-5DC350917501}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Plugin.QuestDB", "Plugin\ThingsGateway.Plugin.QuestDB\ThingsGateway.Plugin.QuestDB.csproj", "{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Plugin.TDengineDB", "Plugin\ThingsGateway.Plugin.TDengineDB\ThingsGateway.Plugin.TDengineDB.csproj", "{2C827B2C-75DF-413B-9AB2-2D1B438AC082}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -231,6 +231,18 @@ Global
|
||||
{C5519C51-0A0C-4317-A43D-FFBB6B344ACB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C5519C51-0A0C-4317-A43D-FFBB6B344ACB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C5519C51-0A0C-4317-A43D-FFBB6B344ACB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7EBD5500-0DA0-415A-831D-5DC350917501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7EBD5500-0DA0-415A-831D-5DC350917501}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7EBD5500-0DA0-415A-831D-5DC350917501}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7EBD5500-0DA0-415A-831D-5DC350917501}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2C827B2C-75DF-413B-9AB2-2D1B438AC082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2C827B2C-75DF-413B-9AB2-2D1B438AC082}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2C827B2C-75DF-413B-9AB2-2D1B438AC082}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2C827B2C-75DF-413B-9AB2-2D1B438AC082}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -242,21 +254,18 @@ Global
|
||||
{566783A4-222B-46F5-AA12-0753997B3254} = {0874CBC5-C583-4FAD-BA93-94571D446898}
|
||||
{BEFBC44A-E140-4E3E-AFBE-2DD8B98EB9BF} = {0874CBC5-C583-4FAD-BA93-94571D446898}
|
||||
{9695B353-D773-40DD-B65E-7B10EB0C16EC} = {0874CBC5-C583-4FAD-BA93-94571D446898}
|
||||
{799C49A4-8E23-475A-A82D-080854718BEE} = {BB9C2A85-7A8A-4CF9-BF44-34DE9848EC15}
|
||||
{616CA361-B667-42C8-B4DC-097C7CD39830} = {79E7042F-F9E3-4D87-BFA9-4B7DD9736735}
|
||||
{16C62A28-BACE-4391-91F8-C2D78D063A1E} = {79E7042F-F9E3-4D87-BFA9-4B7DD9736735}
|
||||
{8FA03089-322F-44CB-8E4B-F2637388E944} = {79E7042F-F9E3-4D87-BFA9-4B7DD9736735}
|
||||
{14FF7150-6DB7-455B-AD00-6AB4DE37855B} = {79E7042F-F9E3-4D87-BFA9-4B7DD9736735}
|
||||
{799C49A4-8E23-475A-A82D-080854718BEE} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{616CA361-B667-42C8-B4DC-097C7CD39830} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{16C62A28-BACE-4391-91F8-C2D78D063A1E} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{8FA03089-322F-44CB-8E4B-F2637388E944} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{14FF7150-6DB7-455B-AD00-6AB4DE37855B} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{2861AA39-AAAE-47ED-9ACC-4C165DDF3EF1} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{9FF2A8A6-48D0-4D8A-9EAD-1905174291CC} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{51313113-7BB8-494E-9C24-6787BECE39BB} = {BB9C2A85-7A8A-4CF9-BF44-34DE9848EC15}
|
||||
{79E7042F-F9E3-4D87-BFA9-4B7DD9736735} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{000C3C62-345E-451C-8CEE-6F2C6A087116} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{5D7BE567-2345-46C8-9F54-DDC1DA96D198} = {000C3C62-345E-451C-8CEE-6F2C6A087116}
|
||||
{5CF1B3EC-84E2-484A-8DFC-2ECD2EE18E2F} = {000C3C62-345E-451C-8CEE-6F2C6A087116}
|
||||
{CD0F211A-F65B-4026-9750-68AC3C70D012} = {000C3C62-345E-451C-8CEE-6F2C6A087116}
|
||||
{5CD79F91-7182-4A9D-9BEF-4DF410C782D2} = {000C3C62-345E-451C-8CEE-6F2C6A087116}
|
||||
{BB9C2A85-7A8A-4CF9-BF44-34DE9848EC15} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{51313113-7BB8-494E-9C24-6787BECE39BB} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{5D7BE567-2345-46C8-9F54-DDC1DA96D198} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{5CF1B3EC-84E2-484A-8DFC-2ECD2EE18E2F} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{CD0F211A-F65B-4026-9750-68AC3C70D012} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{5CD79F91-7182-4A9D-9BEF-4DF410C782D2} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
|
||||
{2057E5BE-FACA-4D44-A2BA-E1F864A8DAFF} = {CC8D0880-B73E-4DFC-9052-86504728708E}
|
||||
{A723D4D7-B796-4D97-BA68-95E5696C9559} = {CC8D0880-B73E-4DFC-9052-86504728708E}
|
||||
{D9944D52-81B4-4DBC-8C3B-2A334CCBD4F6} = {CC8D0880-B73E-4DFC-9052-86504728708E}
|
||||
@@ -269,6 +278,9 @@ Global
|
||||
{681F774F-7B0B-450A-917C-1385E1847CA6} = {237C7BC5-7B07-40B5-AF42-CE2F8E0893C3}
|
||||
{637A662B-7B70-4CE8-8F5F-0A095B9D77EC} = {95008B83-0324-4A7C-80DE-2BBDDD1A9099}
|
||||
{C5519C51-0A0C-4317-A43D-FFBB6B344ACB} = {95008B83-0324-4A7C-80DE-2BBDDD1A9099}
|
||||
{7EBD5500-0DA0-415A-831D-5DC350917501} = {CC8D0880-B73E-4DFC-9052-86504728708E}
|
||||
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB} = {CC8D0880-B73E-4DFC-9052-86504728708E}
|
||||
{2C827B2C-75DF-413B-9AB2-2D1B438AC082} = {CC8D0880-B73E-4DFC-9052-86504728708E}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C49B2D3E-6818-4E28-91B7-6E4E7E264BBB}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>3.0.0.10</Version>
|
||||
<Version>3.0.0.13</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<Authors>Diego</Authors>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>3.0.0.10</Version>
|
||||
<Version>3.0.0.13</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants);ThingsGateway</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);ThingsGateway</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -9,13 +9,14 @@
|
||||
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.8.48" />
|
||||
<PackageReference Include="Furion.Pure" Version="4.8.8.48" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.110" />
|
||||
<PackageReference Include="SqlSugar.TDengineCore" Version="2.8.0" />
|
||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Core\ThingsGateway.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Core\ThingsGateway.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
@@ -38,6 +38,9 @@ public static class Startup
|
||||
{ nameof(PEnqueuedSnackbars.Position), SnackPosition.TopCenter }
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
{ nameof(MErrorHandler), new Dictionary<string, object>() { { nameof(MErrorHandler.ShowDetail), true } } },
|
||||
|
||||
{ nameof(MIcon), new Dictionary<string, object>() { { nameof(MIcon.Dense), true } } },
|
||||
@@ -49,7 +52,7 @@ public static class Startup
|
||||
{ nameof(MDescriptions), new Dictionary<string, object>() { { nameof(MDescriptions.Dense), true } } },
|
||||
{ nameof(MRow), new Dictionary<string, object>() { { nameof(MRow.Dense), true } } },
|
||||
{ "MAutocomplete", new Dictionary<string, object>() { { "Dense", true } } },
|
||||
{ "MCascader", new Dictionary<string, object>() { { "Dense", true } } },
|
||||
{ "MCascader", new Dictionary<string, object>() { { "Dense", true },{ "Outlined", true } } },
|
||||
{ "MCheckbox", new Dictionary<string, object>() { { "Dense", true } } },
|
||||
{ "MFileInput", new Dictionary<string, object>() { { "Dense", true } } },
|
||||
{ "MRadioGroup", new Dictionary<string, object>() { { "Dense", true } } },
|
||||
|
@@ -352,7 +352,54 @@
|
||||
"SortCode": 0,
|
||||
"ExtJson": null
|
||||
},
|
||||
|
||||
{
|
||||
"Id": 447087330930950,
|
||||
"FileName": "ThingsGateway.Plugin.SQLDB",
|
||||
"AssembleName": "SQLDB",
|
||||
"DriverTypeEnum": "Upload",
|
||||
"FilePath": "Plugins/ThingsGateway.Plugin.SQLDB/ThingsGateway.Plugin.SQLDB.dll",
|
||||
"CreateTime": "2023/8/6 18:23:02",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": 212725263002001,
|
||||
"IsDelete": 0,
|
||||
"UpdateTime": null,
|
||||
"UpdateUser": null,
|
||||
"UpdateUserId": null,
|
||||
"SortCode": 0,
|
||||
"ExtJson": null
|
||||
},
|
||||
{
|
||||
"Id": 447087330930951,
|
||||
"FileName": "ThingsGateway.Plugin.QuestDB",
|
||||
"AssembleName": "QuestDB",
|
||||
"DriverTypeEnum": "Upload",
|
||||
"FilePath": "Plugins/ThingsGateway.Plugin.QuestDB/ThingsGateway.Plugin.QuestDB.dll",
|
||||
"CreateTime": "2023/8/6 18:23:02",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": 212725263002001,
|
||||
"IsDelete": 0,
|
||||
"UpdateTime": null,
|
||||
"UpdateUser": null,
|
||||
"UpdateUserId": null,
|
||||
"SortCode": 0,
|
||||
"ExtJson": null
|
||||
},
|
||||
{
|
||||
"Id": 447087330930952,
|
||||
"FileName": "ThingsGateway.Plugin.TDengineDB",
|
||||
"AssembleName": "TDengineDB",
|
||||
"DriverTypeEnum": "Upload",
|
||||
"FilePath": "Plugins/ThingsGateway.Plugin.TDengineDB/ThingsGateway.Plugin.TDengineDB.dll",
|
||||
"CreateTime": "2023/8/6 18:23:02",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": 212725263002001,
|
||||
"IsDelete": 0,
|
||||
"UpdateTime": null,
|
||||
"UpdateUser": null,
|
||||
"UpdateUserId": null,
|
||||
"SortCode": 0,
|
||||
"ExtJson": null
|
||||
},
|
||||
{
|
||||
"Id": 442505,
|
||||
"FileName": "ThingsGateway.Plugin.DLT645",
|
||||
|
@@ -99,9 +99,80 @@ public class HistoryValueWorker : BackgroundService
|
||||
IsAutoCloseConnection = true, //不设成true要手动close
|
||||
ConfigureExternalServices = configureExternalServices,
|
||||
});
|
||||
AopSetting(sqlSugarClient);
|
||||
return OperResult.CreateSuccessResult(sqlSugarClient);
|
||||
}
|
||||
|
||||
|
||||
#region db
|
||||
|
||||
/// <summary>
|
||||
/// Aop设置
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
private static void AopSetting(SqlSugarClient db)
|
||||
{
|
||||
var config = db.CurrentConnectionConfig;
|
||||
|
||||
// 设置超时时间
|
||||
db.Ado.CommandTimeOut = 30;
|
||||
|
||||
// 打印SQL语句
|
||||
db.Aop.OnLogExecuting = (sql, pars) =>
|
||||
{
|
||||
//如果不是开发环境就打印sql
|
||||
if (App.HostEnvironment.IsDevelopment())
|
||||
{
|
||||
if (sql.StartsWith("SELECT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
}
|
||||
if (sql.StartsWith("UPDATE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
}
|
||||
if (sql.StartsWith("INSERT"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
}
|
||||
if (sql.StartsWith("DELETE"))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
}
|
||||
WriteSqlLog(UtilMethods.GetSqlString(config.DbType, sql, pars));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
}
|
||||
};
|
||||
//异常
|
||||
db.Aop.OnError = (ex) =>
|
||||
{
|
||||
//如果不是开发环境就打印日志
|
||||
if (App.WebHostEnvironment.IsDevelopment())
|
||||
{
|
||||
if (ex.Parametres == null) return;
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
|
||||
WriteSqlLogError(UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres));
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
private static void WriteSqlLog(string msg)
|
||||
{
|
||||
Console.WriteLine("【Sql执行时间】:" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
private static void WriteSqlLogError(string msg)
|
||||
{
|
||||
Console.WriteLine("【Sql执行错误时间】:" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
|
||||
Console.WriteLine("【Sql语句】:" + msg + Environment.NewLine);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region worker服务
|
||||
private EasyLock easyLock = new();
|
||||
|
||||
@@ -327,6 +398,7 @@ public class HistoryValueWorker : BackgroundService
|
||||
try
|
||||
{
|
||||
_logger.LogWarning("连接历史数据表失败,尝试初始化表");
|
||||
sqlSugarClient.DbMaintenance.CreateDatabase();
|
||||
sqlSugarClient.CodeFirst.InitTables(typeof(HistoryValue));
|
||||
LastIsSuccess = true;
|
||||
StatuString = OperResult.CreateSuccessResult();
|
||||
@@ -467,7 +539,8 @@ public class HistoryValueMapper : IRegister
|
||||
{
|
||||
config.ForType<DeviceVariableRunTime, HistoryValue>()
|
||||
.Map(dest => dest.Value, (src) => ValueReturn(src))
|
||||
.Map(dest => dest.CollectTime, (src) => src.CollectTime.ToUniversalTime());//注意sqlsugar插入时无时区,直接utc时间
|
||||
.Map(dest => dest.CollectTime, (src) => src.CollectTime.ToUniversalTime())//注意sqlsugar插入时无时区,直接utc时间
|
||||
.Map(dest => dest.CreateTime, (src) => DateTime.UtcNow);//注意sqlsugar插入时无时区,直接utc时间
|
||||
}
|
||||
|
||||
private static object ValueReturn(DeviceVariableRunTime src)
|
||||
|
@@ -27,13 +27,28 @@ public class HistoryValue : PrimaryIdEntity
|
||||
public override long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上传时间
|
||||
/// 采集时间
|
||||
/// </summary>
|
||||
[TimeDbSplitField(DateType.Month)]
|
||||
[Description("上传时间")]
|
||||
[Description("采集时间")]
|
||||
[DataTable(Order = 1, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
|
||||
public DateTime CollectTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上传时间
|
||||
/// </summary>
|
||||
[Description("上传时间")]
|
||||
[DataTable(Order = 1, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "symbol")]
|
||||
[Description("设备名称")]
|
||||
[DataTable(Order = 2, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变量名称
|
||||
/// </summary>
|
||||
@@ -41,6 +56,7 @@ public class HistoryValue : PrimaryIdEntity
|
||||
[Description("变量名称")]
|
||||
[DataTable(Order = 2, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否在线
|
||||
/// </summary>
|
||||
|
@@ -6,7 +6,7 @@ export const language = ["en","zh"];
|
||||
export const removeDefaultStopWordFilter = false;
|
||||
export const removeDefaultStemmer = false;
|
||||
export { default as Mark } from "E:\\Tg\\ThingsGateway\\ThingsGateway-DEV\\handbook\\node_modules\\mark.js\\dist\\mark.js"
|
||||
export const searchIndexUrl = "search-index{dir}.json?_=097027f5";
|
||||
export const searchIndexUrl = "search-index{dir}.json?_=590a19da";
|
||||
export const searchResultLimits = 8;
|
||||
export const searchResultContextMaxLength = 50;
|
||||
export const explicitSearchResultPath = true;
|
||||
|
@@ -227,9 +227,9 @@
|
||||
"179": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/main.5ea8d7cc.js",
|
||||
"hash": "8fef14814f80d395",
|
||||
"publicPath": "/thingsgateway-docs/assets/js/main.5ea8d7cc.js"
|
||||
"file": "assets/js/main.4e65a7c4.js",
|
||||
"hash": "7245ddb629dafe73",
|
||||
"publicPath": "/thingsgateway-docs/assets/js/main.4e65a7c4.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -299,9 +299,9 @@
|
||||
"1303": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/runtime~main.63f259ea.js",
|
||||
"hash": "9366fdc84edb655f",
|
||||
"publicPath": "/thingsgateway-docs/assets/js/runtime~main.63f259ea.js"
|
||||
"file": "assets/js/runtime~main.7f6e9c09.js",
|
||||
"hash": "aedff306f0f845ba",
|
||||
"publicPath": "/thingsgateway-docs/assets/js/runtime~main.7f6e9c09.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -326,9 +326,9 @@
|
||||
"1822": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/f05a39b7.ba620b20.js",
|
||||
"hash": "45780fd223d26c99",
|
||||
"publicPath": "/thingsgateway-docs/assets/js/f05a39b7.ba620b20.js"
|
||||
"file": "assets/js/f05a39b7.bfb8e652.js",
|
||||
"hash": "3d8f498000b417cb",
|
||||
"publicPath": "/thingsgateway-docs/assets/js/f05a39b7.bfb8e652.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -484,9 +484,9 @@
|
||||
"5254": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/aa2a52e0.3e583530.js",
|
||||
"hash": "11f046baf4c7f23f",
|
||||
"publicPath": "/thingsgateway-docs/assets/js/aa2a52e0.3e583530.js"
|
||||
"file": "assets/js/aa2a52e0.8249d909.js",
|
||||
"hash": "740d166c624d8086",
|
||||
"publicPath": "/thingsgateway-docs/assets/js/aa2a52e0.8249d909.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -11,6 +11,9 @@
|
||||
"editUrl": "https://gitee.com/diego2098/ThingsGateway/tree/master/handbook/docs/pluginmc3e.mdx",
|
||||
"tags": [],
|
||||
"version": "current",
|
||||
"lastUpdatedBy": "Kimdiego2098",
|
||||
"lastUpdatedAt": 1697169111,
|
||||
"formattedLastUpdatedAt": "Oct 13, 2023",
|
||||
"frontMatter": {
|
||||
"id": "pluginmc3e",
|
||||
"title": "三菱QnA3E采集插件"
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
handbook/build/assets/js/aa2a52e0.8249d909.js
Normal file
1
handbook/build/assets/js/aa2a52e0.8249d909.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -65,7 +65,7 @@ ThingsGatewayPro是ThingsGateway的加强版本,**基础功能与ThingsGateway
|
||||
|6| 深圳市***自动化技术有限公司 | | 2023-7-16|
|
||||
|7| 西安***信息技术有限责任公司 | | 2023-9-4|
|
||||
|8| 北京****网络科技发展有限公司 | | 2023-9-26|
|
||||
|
||||
|9| 长沙**软件科技有限公司 | | 2023-10-16|
|
||||
|
||||
## 六、购买途径
|
||||
|
||||
|
Reference in New Issue
Block a user