优化代码

This commit is contained in:
2025-09-02 16:13:06 +08:00
parent b37a1fcaf5
commit b770abe3f9

View File

@@ -3,7 +3,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using DMS.Core.Enums; using DMS.Core.Enums;
using DMS.Core.Helper; using DMS.Core.Helper;
using DMS.Core.Models;
using DMS.Helper; using DMS.Helper;
using DMS.Infrastructure.Interfaces.Services; using DMS.Infrastructure.Interfaces.Services;
using DMS.Infrastructure.Models; using DMS.Infrastructure.Models;
@@ -12,198 +11,387 @@ using Opc.Ua;
using Opc.Ua.Client; using Opc.Ua.Client;
using System.Collections; using System.Collections;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using static Dm.net.buffer.ByteArrayBuffer;
namespace DMS.WPF.ViewModels.Dialogs; namespace DMS.WPF.ViewModels.Dialogs;
public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<VariableItemViewModel>> /// <summary>
/// OPC UA导入对话框的视图模型
/// 负责处理OPC UA服务器连接、节点浏览和变量导入等功能
/// </summary>
public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<VariableItemViewModel>>, IDisposable
{ {
/// <summary>
/// OPC UA服务器端点URL
/// </summary>
[ObservableProperty] [ObservableProperty]
private string _endpointUrl = "opc.tcp://127.0.0.1:4855"; // 默认值 private string _endpointUrl = "opc.tcp://127.0.0.1:4855"; // 默认值
/// <summary>
/// OPC UA根节点
/// </summary>
[ObservableProperty] [ObservableProperty]
private OpcUaNodeItemViewModel _rootOpcUaNode; private OpcUaNodeItemViewModel _rootOpcUaNode;
/// <summary>
/// 当前选中节点下的所有变量集合
/// </summary>
[ObservableProperty] [ObservableProperty]
private ObservableCollection<VariableItemViewModel> _opcUaNodeVariables = new ObservableCollection<VariableItemViewModel>(); private ObservableCollection<VariableItemViewModel> _opcUaNodeVariables = new();
/// <summary>
/// 用户选择的变量列表
/// </summary>
[ObservableProperty] [ObservableProperty]
private IList _selectedVariables = new ArrayList(); private IList _selectedVariables = new ArrayList();
/// <summary>
/// 是否全选变量
/// </summary>
[ObservableProperty] [ObservableProperty]
private bool _selectAllVariables; private bool _selectAllVariables;
/// <summary>
/// 是否已连接到OPC UA服务器
/// </summary>
[ObservableProperty] [ObservableProperty]
private bool _isConnected; private bool _isConnected;
/// <summary>
/// 连接按钮显示文本
/// </summary>
[ObservableProperty] [ObservableProperty]
private string _connectButtonText = "连接服务器"; private string _connectButtonText = "连接服务器";
/// <summary>
/// 连接按钮是否可用
/// </summary>
[ObservableProperty] [ObservableProperty]
private bool _isConnectButtonEnabled = true; private bool _isConnectButtonEnabled = true;
/// <summary>
/// 当前选中的OPC UA节点
/// </summary>
[ObservableProperty] [ObservableProperty]
private OpcUaNodeItemViewModel _currentOpcUaNodeItem; [NotifyCanExecuteChangedFor(nameof(FindCurrentNodeVariablesCommand))] // 当选中节点改变时通知查找变量命令更新可执行状态
private OpcUaNodeItemViewModel _selectedNode;
private Session _session;
/// <summary>
/// OPC UA服务接口实例
/// </summary>
private readonly IOpcUaService _opcUaService; private readonly IOpcUaService _opcUaService;
/// <summary>
/// 对象映射器实例
/// </summary>
private readonly IMapper _mapper; private readonly IMapper _mapper;
private CancellationTokenSource _cancellationTokenSource;
/// <summary>
/// 取消令牌源,用于取消长时间运行的操作
/// </summary>
private readonly CancellationTokenSource _cancellationTokenSource;
/// <summary>
/// 构造函数
/// 初始化ImportOpcUaDialogViewModel实例
/// </summary>
/// <param name="opcUaService">OPC UA服务接口实例</param>
/// <param name="mapper">对象映射器实例</param>
public ImportOpcUaDialogViewModel(IOpcUaService opcUaService, IMapper mapper) public ImportOpcUaDialogViewModel(IOpcUaService opcUaService, IMapper mapper)
{ {
this._opcUaService = opcUaService; _opcUaService = opcUaService;
this._mapper = mapper; _mapper = mapper;
// 初始化根节点
RootOpcUaNode = new OpcUaNodeItemViewModel() { DisplayName = "根节点", NodeId = Objects.ObjectsFolder, IsExpanded = true }; RootOpcUaNode = new OpcUaNodeItemViewModel() { DisplayName = "根节点", NodeId = Objects.ObjectsFolder, IsExpanded = true };
// 初始化取消令牌源
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
} }
/// <summary>
/// 查找当前节点变量命令的可执行条件
/// 只有在已连接且有选中节点时才可执行
/// </summary>
public bool CanFindCurrentNodeVariables => IsConnected && SelectedNode != null;
/// <summary>
/// 连接到OPC UA服务器命令
/// 负责建立与OPC UA服务器的连接并加载根节点信息
/// </summary>
[RelayCommand] [RelayCommand]
private async Task Connect() private async Task Connect()
{ {
try try
{ {
// 断开现有连接 // 更新UI状态禁用连接按钮并显示连接中状态
if (!_opcUaService.IsConnected) IsConnectButtonEnabled = false;
{ ConnectButtonText = "连接中...";
await _opcUaService.ConnectAsync(EndpointUrl);
}
// 异步连接到OPC UA服务器
await _opcUaService.ConnectAsync(EndpointUrl);
// 检查连接是否成功建立
if (_opcUaService.IsConnected) if (_opcUaService.IsConnected)
{ {
// 更新连接状态
IsConnected = true; IsConnected = true;
ConnectButtonText = "已连接"; ConnectButtonText = "已连接";
IsConnectButtonEnabled = false;
// 浏览根节点并加载其子节点
var children = await _opcUaService.BrowseNode(_mapper.Map<OpcUaNode>(RootOpcUaNode));
RootOpcUaNode.Children = _mapper.Map<ObservableCollection<OpcUaNodeItemViewModel>>(children);
} }
}
// 处理特定异常类型提供更友好的用户提示
// 浏览根节点 catch (UnauthorizedAccessException ex)
{
var childrens = await _opcUaService.BrowseNode(_mapper.Map<OpcUaNode>(RootOpcUaNode)); NotificationHelper.ShowError($"连接被拒绝,请检查用户名和密码: {ex.Message}");
RootOpcUaNode.Children = _mapper.Map<ObservableCollection<OpcUaNodeItemViewModel>>(childrens); }
catch (TimeoutException ex)
{
NotificationHelper.ShowError($"连接超时,请检查服务器地址和网络连接: {ex.Message}");
} }
catch (Exception ex) catch (Exception ex)
{ {
IsConnected = false;
IsConnectButtonEnabled = false;
ConnectButtonText = "连接服务器";
NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex); NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex);
} }
} finally
{
[RelayCommand] // 确保按钮状态正确更新
private async void SecondaryButton() // 如果连接失败,恢复按钮为可用状态
{ if (!IsConnected)
await _opcUaService.DisconnectAsync(); {
IsConnectButtonEnabled = true;
Close(SelectedVariables.Cast<VariableItemViewModel>().ToList()); ConnectButtonText = "连接服务器";
} }
}
[RelayCommand]
private async void PrimaryButton()
{
await _opcUaService.DisconnectAsync();
Close(OpcUaNodeVariables.ToList());
}
[RelayCommand]
private async void CloseButton()
{
await _opcUaService.DisconnectAsync();
} }
/// <summary> /// <summary>
/// 处理来自服务器的数据变化通知 /// 次要按钮命令(取消导入)
/// 负责断开与OPC UA服务器的连接并关闭对话框返回用户选择的变量
/// </summary> /// </summary>
private static void OnNotification(MonitoredItem item, MonitoredItemNotificationEventArgs e) [RelayCommand]
private async Task SecondaryButton()
{ {
foreach (var value in item.DequeueValues())
{
Console.WriteLine(
$"[通知] {item.DisplayName}: {value.Value} | 时间戳: {value.SourceTimestamp.ToLocalTime()} | 状态: {value.StatusCode}");
}
}
public async Task LoadNodeVariables(OpcUaNodeItemViewModel node)
{
try try
{ {
OpcUaNodeVariables.Clear(); // 断开与OPC UA服务器的连接
await _opcUaService.DisconnectAsync();
// 加载节点的子项 // 关闭对话框并返回用户选择的变量列表
node.IsExpanded = true; Close(SelectedVariables.Cast<VariableItemViewModel>().ToList());
node.IsSelected = true;
CurrentOpcUaNodeItem = node;
await Browse(node);
} }
catch (Exception ex) catch (Exception ex)
{ {
NotificationHelper.ShowError($"加载 OPC UA 节点变量失败: {node.NodeId} - {ex.Message}", ex); NotificationHelper.ShowError($"断开连接时发生错误: {ex.Message}", ex);
}
}
private async Task Browse(OpcUaNodeItemViewModel node, bool isScan = false)
{
var childrens = await _opcUaService.BrowseNode(_mapper.Map<OpcUaNode>(node));
foreach (var children in childrens)
{
var opcNodeItem = _mapper.Map<OpcUaNodeItemViewModel>(children);
if (children.NodeClass == NodeClass.Variable)
{
OpcUaNodeVariables.Add(new VariableItemViewModel
{
Name = children.DisplayName, // 修正:使用子节点的显示名称
OpcUaNodeId = children.NodeId.ToString(),
Protocol = ProtocolType.OpcUa,
CSharpDataType = children.DataType,
IsActive = true // 默认选中
});
}
else
{
if (node.Children.FirstOrDefault(n => n.NodeId == opcNodeItem.NodeId) == null)
{
node.Children.Add(opcNodeItem);
}
if (isScan)
{
Browse(opcNodeItem);
}
}
} }
} }
/// <summary>
/// 主要按钮命令(确认导入)
/// 负责断开与OPC UA服务器的连接并关闭对话框返回所有加载的变量
/// </summary>
[RelayCommand] [RelayCommand]
private async Task PrimaryButton()
{
try
{
// 断开与OPC UA服务器的连接
await _opcUaService.DisconnectAsync();
// 关闭对话框并返回所有加载的变量
Close(OpcUaNodeVariables.ToList());
}
catch (Exception ex)
{
NotificationHelper.ShowError($"断开连接时发生错误: {ex.Message}", ex);
}
}
/// <summary>
/// 关闭按钮命令
/// 负责断开与OPC UA服务器的连接
/// </summary>
[RelayCommand]
private async Task CloseButton()
{
try
{
// 断开与OPC UA服务器的连接
await _opcUaService.DisconnectAsync();
}
catch (Exception ex)
{
NotificationHelper.ShowError($"断开连接时发生错误: {ex.Message}", ex);
}
}
/// <summary>
/// 查找当前节点变量命令
/// 根据当前选中的节点查找其下的所有变量
/// </summary>
[RelayCommand(CanExecute = nameof(CanFindCurrentNodeVariables))]
private async Task FindCurrentNodeVariables() private async Task FindCurrentNodeVariables()
{ {
try try
{ {
if (CurrentOpcUaNodeItem == null) // 检查是否有选中的节点
if (SelectedNode == null)
{ {
NotificationHelper.ShowError($"请先选择左边的节点,然后再查找变量。"); NotificationHelper.ShowError("请先选择左边的节点,然后再查找变量。");
return; return;
} }
// 清空当前变量列表
OpcUaNodeVariables.Clear(); OpcUaNodeVariables.Clear();
// 加载节点的子项 // 设置选中节点的状态
CurrentOpcUaNodeItem.IsExpanded = true; SelectedNode.IsExpanded = true;
CurrentOpcUaNodeItem.IsSelected = true; SelectedNode.IsSelected = true;
await Browse(CurrentOpcUaNodeItem, true); // 异步浏览节点变量(递归模式)
await BrowseNodeVariablesAsync(SelectedNode, true);
}
catch (OperationCanceledException)
{
// 处理用户取消操作的情况
NotificationHelper.ShowInfo("操作已被取消");
} }
catch (Exception ex) catch (Exception ex)
{ {
NotificationHelper.ShowError($"加载 OPC UA 节点变量失败: {CurrentOpcUaNodeItem.NodeId} - {ex.Message}", ex); // 处理其他异常情况
NotificationHelper.ShowError($"加载 OPC UA 节点变量失败: {SelectedNode?.NodeId} - {ex.Message}", ex);
} }
} }
/// <summary>
/// 加载节点变量方法
/// 根据指定节点加载其下的所有变量
/// </summary>
/// <param name="node">要加载变量的OPC UA节点</param>
public async Task LoadNodeVariables(OpcUaNodeItemViewModel node)
{
try
{
// 清空当前变量列表
OpcUaNodeVariables.Clear();
// 设置节点状态
node.IsExpanded = true;
node.IsSelected = true;
// 更新选中节点
SelectedNode = node;
// 异步浏览节点变量(非递归模式)
await BrowseNodeVariablesAsync(node);
}
catch (OperationCanceledException)
{
// 处理用户取消操作的情况
NotificationHelper.ShowInfo("操作已被取消");
}
catch (Exception ex)
{
// 处理其他异常情况
NotificationHelper.ShowError($"加载 OPC UA 节点变量失败: {node?.NodeId} - {ex.Message}", ex);
}
}
/// <summary>
/// 浏览节点变量异步方法
/// 递归或非递归地浏览指定节点下的所有变量
/// </summary>
/// <param name="node">要浏览的节点</param>
/// <param name="isRecursive">是否递归浏览子节点</param>
private async Task BrowseNodeVariablesAsync(OpcUaNodeItemViewModel node, bool isRecursive = false)
{
// 参数有效性检查
if (node == null) return;
try
{
// 检查是否有取消请求
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
// 异步浏览节点获取子节点列表
var children = await _opcUaService.BrowseNode(_mapper.Map<OpcUaNode>(node));
// 再次检查是否有取消请求
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
// 遍历所有子节点
foreach (var child in children)
{
// 映射子节点为视图模型对象
var nodeItem = _mapper.Map<OpcUaNodeItemViewModel>(child);
// 判断节点类型是否为变量
if (child.NodeClass == NodeClass.Variable)
{
// 创建并添加变量项到变量列表
OpcUaNodeVariables.Add(new VariableItemViewModel
{
Name = child.DisplayName, // 变量名称
OpcUaNodeId = child.NodeId.ToString(), // OPC UA节点ID
Protocol = ProtocolType.OpcUa, // 协议类型
CSharpDataType = child.DataType, // C#数据类型
IsActive = true // 默认激活状态
});
}
// 如果是递归模式且节点不是变量,则递归浏览子节点
else if (isRecursive)
{
// 递归浏览子节点
await BrowseNodeVariablesAsync(nodeItem, true);
}
// 非递归模式下,将非变量节点添加到节点树中
else
{
// 避免重复添加相同节点
if (!node.Children.Any(n => n.NodeId == nodeItem.NodeId))
{
node.Children.Add(nodeItem);
}
}
}
}
catch (OperationCanceledException)
{
// 处理取消操作
NlogHelper.Info("节点浏览操作已被取消");
throw; // 重新抛出异常以保持调用链
}
catch (Exception ex)
{
// 记录浏览节点失败的日志
NlogHelper.Error($"浏览节点失败: {node.NodeId} - {ex.Message}", ex);
throw; // 重新抛出异常以保持调用链
}
}
/// <summary>
/// 处理来自服务器的数据变化通知
/// 当监视的OPC UA节点数据发生变化时会被调用
/// </summary>
/// <param name="item">发生变化的监视项</param>
/// <param name="e">监视项通知事件参数</param>
private static void OnNotification(MonitoredItem item, MonitoredItemNotificationEventArgs e)
{
// 遍历所有变化的值
foreach (var value in item.DequeueValues())
{
// 输出通知信息到控制台
Console.WriteLine(
$@"[通知] {item.DisplayName}: {value.Value} | 时间戳: {value.SourceTimestamp.ToLocalTime()} | 状态: {value.StatusCode}");
}
}
/// <summary>
/// 释放资源方法
/// 实现IDisposable接口负责释放使用的资源
/// </summary>
public void Dispose()
{
// 发出取消请求
_cancellationTokenSource?.Cancel();
// 释放取消令牌源资源
_cancellationTokenSource?.Dispose();
}
} }