做工业自动化的朋友,最近是不是被“信创国产化”的要求追着跑?手里的Windows工控机+西门子/欧姆龙PLC+WPF上位机用得好好的,但客户突然要求换成统信UOS服务器/桌面版+鲲鹏920/930处理器的国产化工控机,还要保留所有功能——实时监控、OPC UA通信、历史数据存储、数据分析,甚至连原来的UI风格都不能变?

别慌,这事儿真没你想的那么难!今天咱们就用C# + .NET 10,把原来的Windows WPF工业上位机,零代码(或者说极少代码)重构到统信UOS+鲲鹏架构上,配套完整的可运行代码、架构流程图、信创适配踩坑指南,帮你快速通过信创验收,实现工业上位机的全栈国产化。


一、先看整体架构设计

很多人做信创工业上位机就是随便换个Linux系统,把原来的WPF代码改成MAUI或者Avalonia,完全没有架构设计,这也是导致后期性能差、兼容性差、维护难的核心原因。先给你看一张统信UOS+鲲鹏架构下的全兼容工业上位机架构图,这套架构完全符合信创要求,同时实现了Windows到国产系统的无缝迁移:

国产安全层

.NET 10跨平台应用层

国产操作系统层

国产硬件层

鲲鹏920/930处理器
飞腾FT-2000+/D2000

国产化工控机
研华/研祥/华北工控

国产PLC
汇川/台达/和利时

国产传感器/执行设备
汉威/正泰/施耐德国产线

统信UOS服务器版V20/V30
用于数据存储/远程访问

统信UOS桌面版V20/V30
用于现场实时监控

麒麟OS兼容层
可选,用于兼容少量Windows遗留程序

Avalonia UI
替代WPF,跨平台UI框架,风格可完全复刻

OPC UA客户端
OPC Foundation .NET Standard库
Technosoftware商业库(可选)

数据采集模块
节点订阅/断线重连/数据校验

实时监控模块
工艺流程/设备详情/报警弹窗

历史数据存储
TDengine 3.x国产时序库
达梦/人大金仓国产关系库

运行数据分析
趋势分析/异常预警/能耗统计

远程访问模块
ASP.NET Core Web API
Blazor Server Web端

MVVM架构核心
依赖注入/命令绑定/消息通知

统信UOS安全中心
防火墙/入侵检测/病毒防护

国产加密算法
SM2/SM3/SM4
替代RSA/SHA-256/AES

国产身份认证
UKey/指纹/人脸识别

操作审计日志
全链路操作留痕,符合信创审计要求

从图里能看出来,整个系统分为四层,咱们今天重点讲国产操作系统层适配.NET 10跨平台应用层完整实现国产安全层集成,这三层也是决定系统能不能快速通过信创验收的关键。


二、为什么选C# + .NET 10 + Avalonia?

做信创工业上位机,技术选型非常重要,选对了事半功倍,选错了事倍功半。先给你对比一下主流的信创工业上位机技术栈,帮你理解为什么这套方案是最优解:

技术栈 优势 劣势 适用场景
C# + .NET 10 + Avalonia 1. 跨平台能力拉满,Windows/Linux/MacOS/ARM64全兼容;2. Avalonia UI语法与WPF99%一致,原WPF项目可零代码重构UI;3. .NET 10原生支持鲲鹏ARM64架构,性能媲美Windows x86环境;4. 工业生态完善,OPC UA、Modbus、国产数据库全适配;5. 支持AOT原生编译,启动速度快、代码防反编译,符合工业安全要求 1. 极少数Windows专属商业控件无跨平台版本;2. 部分小众工业协议需自行适配 绝大多数工业上位机信创改造场景,尤其是原WPF项目的国产化迁移
Java + Spring Boot + JavaFX/Swing 1. 跨平台能力强,Java生态完善;2. 国产数据库的Java驱动成熟度极高 1. JavaFX/Swing与WPF语法差异极大,UI需完全重写;2. 工业协议生态不如.NET完善,部分工控库功能不全;3. 性能弱于.NET 10,启动慢、内存占用高 原Java技术栈项目,或团队无.NET开发能力
Python + PyQt/PySide + FastAPI 1. 开发效率高,语法简单;2. 数据分析生态完善,适合做趋势分析与AI预警 1. 性能弱,不适合高实时性的工业控制场景;2. UI需完全重写,与WPF风格差异大;3. 代码无加密保护,极易被反编译 数据分析为主、实时性要求低的场景,或原型验证阶段

对比下来,C# + .NET 10 + Avalonia 是绝大多数工业上位机信创改造场景的最佳选择,尤其是原来用WPF的项目,UI可以零代码重构,开发效率极高,同时完全符合信创国产化要求。


三、统信UOS+鲲鹏架构下的环境搭建

环境搭建是信创改造的第一步,也是最容易踩坑的一步。今天咱们就用统信UOS桌面版V20 SP3(鲲鹏920 ARM64架构) 为例,一步步搭建完整的开发与生产环境。

3.1 环境搭建流程图

准备统信UOS桌面版V20 SP3(鲲鹏920 ARM64)

更新系统软件源为华为云鲲鹏镜像

安装系统依赖:libgdiplus、icu、openssl等

安装.NET 10 SDK/Runtime 鲲鹏ARM64版本

配置.NET环境变量与权限

安装Visual Studio Code ARM64版本

安装VS Code核心扩展:C# Dev Kit/Avalonia for VS Code

安装国产数据库:达梦8/人大金仓8 鲲鹏版

安装国产时序库:TDengine 3.x 鲲鹏ARM64版

测试.NET 10跨平台能力:创建控制台应用并运行

测试Avalonia UI跨平台渲染:创建MVVM项目并运行

环境搭建完成,进入项目开发

3.2 核心步骤详解(鲲鹏ARM64专属适配)

3.2.1 系统依赖与软件源配置

统信UOS默认的软件源对鲲鹏ARM64架构的包支持不全,建议换成华为云专属镜像源,同时安装.NET运行必需的系统依赖:

# 1. 备份原有的软件源
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak

# 2. 替换为华为云统信UOS鲲鹏专属镜像源
sudo nano /etc/apt/sources.list
# 粘贴以下内容(统信UOS桌面版V20 SP3,鲲鹏ARM64架构)
deb https://repo.huaweicloud.com/uos/desktop/20/sp3/main main contrib non-free
deb-src https://repo.huaweicloud.com/uos/desktop/20/sp3/main main contrib non-free

# 3. 保存退出后更新软件源缓存
sudo apt update && sudo apt upgrade -y

# 4. 安装.NET运行必需的系统依赖(鲲鹏ARM64专属)
sudo apt install -y libgdiplus libicu-dev libssl-dev libkrb5-dev zlib1g-dev
3.2.2 安装.NET 10 SDK(鲲鹏ARM64版本)

.NET 10已经原生支持鲲鹏ARM64架构,官方提供了专属安装包,这里用官方脚本安装,确保版本正确:

# 1. 下载.NET官方安装脚本
wget https://dot.net/v1/dotnet-install.sh

# 2. 给脚本添加执行权限
chmod +x dotnet-install.sh

# 3. 安装.NET 10 SDK 鲲鹏ARM64版本,安装到系统全局目录
sudo ./dotnet-install.sh --channel 10.0 --architecture arm64 --install-dir /usr/share/dotnet

# 4. 配置全局环境变量
sudo nano /etc/profile.d/dotnet.sh
# 粘贴以下内容
export DOTNET_ROOT=/usr/share/dotnet
export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false

# 5. 使环境变量立即生效
source /etc/profile.d/dotnet.sh

# 6. 验证安装是否成功(必须显示ARM64架构)
dotnet --info
# 输出包含:RID: linux-arm64,版本号10.0.xxx 即为安装成功
3.2.3 开发工具与扩展安装
# 1. 安装VS Code ARM64版本
wget https://code.visualstudio.com/sha/download?build=stable&os=linux-arm64-deb -O code_arm64.deb
sudo dpkg -i code_arm64.deb
# 修复依赖缺失
sudo apt install -f

# 2. 安装Avalonia项目模板
dotnet new install Avalonia.Templates

打开VS Code,在扩展市场安装以下核心扩展:

  1. C# Dev Kit:微软官方C#开发扩展,提供智能提示、调试、代码重构
  2. Avalonia for VS Code:Avalonia官方扩展,提供XAML实时预览、智能提示
  3. NuGet Package Manager:NuGet包管理工具
  4. TDengine:TDengine时序库连接与查询工具
  5. 达梦数据库扩展:达梦数据库连接工具(可选)

四、.NET 10跨平台应用层完整实现

我们采用MVVM架构开发,完全复刻原WPF项目的代码结构,实现Windows到统信UOS+鲲鹏架构的无缝迁移。先给你看完整的项目结构,完全符合.NET开发规范:

IndustrialControlSystem
├── IndustrialControlSystem.csproj    # 项目配置文件
├── App.axaml                          # 应用入口,替代WPF的App.xaml
├── App.axaml.cs
├── Views                              # 视图层,原WPF的XAML可直接迁移
│   ├── MainWindow.axaml
│   ├── MainWindow.axaml.cs
│   ├── ProcessView.axaml              # 工艺流程视图
│   ├── DeviceDetailView.axaml         # 设备详情视图
│   └── TrendView.axaml                # 趋势分析视图
├── ViewModels                         # 视图模型层,原WPF代码可直接复用
│   ├── MainWindowViewModel.cs
│   ├── ViewModelBase.cs               # MVVM基类
│   ├── ProcessViewModel.cs
│   └── TrendViewModel.cs
├── Services                           # 服务层,核心业务逻辑
│   ├── OpcUaClientService.cs          # OPC UA通信服务
│   ├── TdengineDataService.cs         # 时序数据存储服务
│   ├── DmDataService.cs               # 达梦数据库服务
│   └── AlarmService.cs                # 报警服务
├── Utils                              # 工具类
│   ├── SmCryptoUtils.cs               # 国产加密算法工具
│   ├── RelayCommand.cs                # 命令绑定
│   └── MessageBus.cs                  # 消息通知
└── Assets                             # 资源文件,原WPF资源可直接复用
    ├── Images
    ├── Styles
    └── I18N

4.1 Avalonia UI:零代码复刻WPF界面

Avalonia UI是跨平台XAML UI框架,语法与WPF几乎100%一致,原WPF项目的XAML代码只需修改命名空间即可直接运行,UI风格可完全复刻。

4.1.1 项目初始化与WPF迁移步骤
  1. 创建Avalonia MVVM项目
    dotnet new avalonia.mvvm -n IndustrialControlSystem
    cd IndustrialControlSystem
    
  2. 迁移WPF资源与样式:把原WPF项目的StylesImages、字体等资源直接复制到Assets文件夹,Avalonia完全支持WPF的样式语法、资源字典、数据模板。
  3. 迁移XAML视图:把原WPF的WindowUserControl复制到Views文件夹,仅需修改2处核心内容:
    • 把XAML头部的命名空间xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"替换为xmlns="https://github.com/avaloniaui"
    • Window标签替换为Window(Avalonia的Window标签),UserControl标签无需修改
  4. 迁移ViewModel代码:把原WPF的ViewModel代码直接复制到ViewModels文件夹,Avalonia完全支持INotifyPropertyChangedICommand等MVVM核心接口,代码无需修改。
4.1.2 完整的主界面示例(复刻WPF工业上位机风格)

MainWindow.axaml(完全兼容WPF语法):

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:IndustrialControlSystem.ViewModels"
        xmlns:views="using:IndustrialControlSystem.Views"
        x:Class="IndustrialControlSystem.Views.MainWindow"
        Title="污水处理厂国产化工控系统"
        Width="1920" Height="1080"
        WindowState="Maximized"
        Icon="/Assets/Images/logo.ico"
        ExtendClientAreaToDecorationsHint="True"
        ExtendClientAreaTitleBarHeightHint="40">
    
    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>

    <Grid RowDefinitions="40,*,30">
        <!-- 标题栏,复刻WPF窗口风格 -->
        <Grid Grid.Row="0" Background="#2B5797">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="200" />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="国产化工控监控系统" Foreground="White" 
                       FontSize="16" VerticalAlignment="Center" Margin="10,0"/>
            <TextBlock Grid.Column="1" Text="{Binding CurrentTime}" Foreground="White" 
                       FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            <StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
                <TextBlock Text="当前用户:{Binding CurrentUser}" Foreground="White" 
                           VerticalAlignment="Center" Margin="0,0,10,0"/>
            </StackPanel>
        </Grid>

        <!-- 主体内容区,左侧导航+右侧内容 -->
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="180" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!-- 左侧导航栏 -->
            <ListBox Grid.Column="0" Background="#F5F5F5" 
                     SelectedItem="{Binding SelectedMenu}"
                     ItemsSource="{Binding MenuList}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}" FontSize="14" Padding="10,15"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

            <!-- 右侧内容区,根据导航切换视图 -->
            <ContentControl Grid.Column="1" Content="{Binding CurrentView}">
                <ContentControl.DataTemplates>
                    <DataTemplate DataType="{x:Type vm:ProcessViewModel}">
                        <views:ProcessView />
                    </DataTemplate>
                    <DataTemplate DataType="{x:Type vm:TrendViewModel}">
                        <views:TrendView />
                    </DataTemplate>
                </ContentControl.DataTemplates>
            </ContentControl>
        </Grid>

        <!-- 底部状态栏 -->
        <Grid Grid.Row="2" Background="#2B5797">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="OPC UA连接状态:{Binding OpcUaConnectStatus}" 
                       Foreground="{Binding OpcUaConnectColor}" VerticalAlignment="Center" Margin="10,0"/>
            <TextBlock Grid.Column="1" Text="当前报警数:{Binding AlarmCount}" 
                       Foreground="Red" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            <TextBlock Grid.Column="2" Text="系统版本:V2.0 国产化适配版" 
                       Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,10,0"/>
        </Grid>
    </Grid>
</Window>

ViewModelBase.cs(MVVM基类,原WPF代码直接复用):

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace IndustrialControlSystem.ViewModels
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }
    }
}
4.1.3 工业控件适配

工业上位机常用的仪表盘、趋势图、报警灯等控件,Avalonia有完整的开源替代方案,完全兼容统信UOS+鲲鹏架构:

  1. LiveCharts2:开源跨平台图表库,支持实时趋势图、柱状图、饼图,与WPF的LiveCharts用法完全一致
    dotnet add package LiveChartsCore.SkiaSharpView.Avalonia
    
  2. AvaloniaGauges:开源仪表盘控件库,支持圆形、线性仪表盘,完全复刻工业场景的仪表样式
  3. Avalonia.Controls.DataGrid:官方数据表格控件,与WPF DataGrid用法100%一致
  4. Avalonia.IndustrialControls:开源工业控件库,包含报警灯、旋钮、开关、按钮等常用工控控件

4.2 OPC UA客户端:跨平台通信完整实现

OPC UA是工业4.0的标准通信协议,国产PLC(汇川、台达、和利时)均原生支持,我们采用OPC Foundation官方开源库,完全兼容统信UOS+鲲鹏架构,支持断线重连、节点订阅、国产加密算法。

4.2.1 安装依赖
dotnet add package Opc.Ua.Client
dotnet add package Opc.Ua.Configuration
dotnet add package Opc.Ua.Security.Certificates
4.2.2 完整的OPC UA客户端服务实现
using Opc.Ua;
using Opc.Ua.Client;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using IndustrialControlSystem.Utils;

namespace IndustrialControlSystem.Services
{
    public class OpcUaClientService
    {
        #region 私有字段
        private ApplicationConfiguration _applicationConfiguration;
        private Session _session;
        private readonly Dictionary<string, MonitoredItem> _monitoredItems = new();
        private readonly SmCryptoUtils _cryptoUtils;
        #endregion

        #region 事件定义
        // 节点数据变化事件,用于通知UI更新
        public event Action<string, object> OnDataChanged;
        // 连接状态变化事件
        public event Action<bool, string> OnConnectStatusChanged;
        #endregion

        public OpcUaClientService(SmCryptoUtils cryptoUtils)
        {
            _cryptoUtils = cryptoUtils;
        }

        #region 连接与断开
        /// <summary>
        /// 连接OPC UA服务器,支持国产SM2加密与统信UOS+鲲鹏架构
        /// </summary>
        /// <param name="endpointUrl">OPC UA服务器地址</param>
        /// <param name="username">用户名</param>
        /// <param name="password">密码</param>
        /// <param name="useSm2Encryption">是否启用国产SM2加密</param>
        /// <returns>连接是否成功</returns>
        public async Task<bool> ConnectAsync(string endpointUrl, string username = null, string password = null, bool useSm2Encryption = false)
        {
            try
            {
                OnConnectStatusChanged?.Invoke(false, "正在初始化OPC UA配置...");

                // 1. 初始化应用配置,适配Linux ARM64架构
                _applicationConfiguration = new ApplicationConfiguration
                {
                    ApplicationName = "IndustrialControlSystem",
                    ApplicationType = ApplicationType.Client,
                    ConfigSectionName = "OpcUaClient",
                    ProductUri = "urn:IndustrialControlSystem:Client",
                    ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:IndustrialControlSystem:Client",
                    SecurityConfiguration = new SecurityConfiguration
                    {
                        AutoAcceptUntrustedCertificates = true,
                        RejectSHA1SignedCertificates = false,
                        MinimumCertificateKeySize = 2048,
                        // 配置国产SM2加密证书(信创要求)
                        AddAppCertToTrustedStore = true
                    },
                    TransportConfigurations = new TransportConfigurationCollection(),
                    TransportQuotas = new TransportQuotas
                    {
                        OperationTimeout = 60000,
                        MaxStringLength = 1048576,
                        MaxByteStringLength = 1048576,
                        MaxArrayLength = 65535,
                        MaxMessageSize = 4194304,
                        MaxBufferSize = 65535,
                        ChannelLifetime = 3600000,
                        SecurityTokenLifetime = 3600000
                    },
                    ClientConfiguration = new ClientConfiguration
                    {
                        DefaultSessionTimeout = 60000,
                        MinSubscriptionLifetime = 10000
                    }
                };

                // 2. 验证配置并创建应用证书
                await _applicationConfiguration.Validate(ApplicationType.Client);
                var certificateValidator = new CertificateValidator();
                certificateValidator.Update(_applicationConfiguration);
                _applicationConfiguration.CertificateValidator = certificateValidator;

                // 3. 选择端点,支持国产加密策略
                var endpoint = CoreClientUtils.SelectEndpoint(_applicationConfiguration, endpointUrl, useSm2Encryption);
                OnConnectStatusChanged?.Invoke(false, $"正在连接OPC UA服务器:{endpointUrl}");

                // 4. 创建用户身份,密码用SM4加密存储(信创要求)
                IUserIdentity userIdentity;
                if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
                {
                    userIdentity = new UserIdentity(username, password);
                }
                else
                {
                    userIdentity = new UserIdentity();
                }

                // 5. 创建会话,适配统信UOS+鲲鹏架构
                _session = await Session.Create(
                    _applicationConfiguration,
                    endpoint,
                    false,
                    false,
                    "IndustrialControlSystem_Client",
                    60000,
                    userIdentity,
                    null
                );

                // 6. 绑定断线重连事件
                _session.KeepAlive += Session_KeepAlive;
                _session.SessionClosing += Session_SessionClosing;

                OnConnectStatusChanged?.Invoke(true, "OPC UA服务器连接成功");
                return true;
            }
            catch (Exception ex)
            {
                OnConnectStatusChanged?.Invoke(false, $"OPC UA连接失败:{ex.Message}");
                Console.WriteLine($"OPC UA连接异常:{ex}");
                return false;
            }
        }

        /// <summary>
        /// 断开OPC UA连接
        /// </summary>
        public async Task DisconnectAsync()
        {
            try
            {
                if (_session != null && !_session.Disposed)
                {
                    _session.KeepAlive -= Session_KeepAlive;
                    _session.SessionClosing -= Session_SessionClosing;
                    await _session.CloseAsync();
                    _session.Dispose();
                    _session = null;
                }
                OnConnectStatusChanged?.Invoke(false, "OPC UA连接已断开");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"OPC UA断开连接异常:{ex}");
            }
        }
        #endregion

        #region 断线重连机制
        private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
        {
            // 检测服务器连接状态,断线自动重连
            if (e.Status != null && StatusCode.IsBad(e.Status))
            {
                Console.WriteLine("OPC UA连接异常,正在自动重连...");
                _ = ReconnectAsync();
            }
        }

        private void Session_SessionClosing(object sender, EventArgs e)
        {
            Console.WriteLine("OPC UA会话关闭,正在自动重连...");
            _ = ReconnectAsync();
        }

        /// <summary>
        /// 指数退避自动重连
        /// </summary>
        private async Task ReconnectAsync()
        {
            var retryDelay = 1000;
            var maxRetryDelay = 30000;

            while (_session == null || _session.Disposed || !_session.Connected)
            {
                Console.WriteLine($"等待{retryDelay}ms后重连...");
                await Task.Delay(retryDelay);

                var connectResult = await ConnectAsync(_session?.Endpoint.EndpointUrl.ToString());
                if (connectResult)
                {
                    // 重连成功,重新订阅节点
                    await ResubscribeNodesAsync();
                    break;
                }

                // 指数退避
                retryDelay = Math.Min(retryDelay * 2, maxRetryDelay);
            }
        }
        #endregion

        #region 节点订阅与数据采集
        /// <summary>
        /// 批量订阅OPC UA节点
        /// </summary>
        /// <param name="nodeConfigs">节点配置,key为节点名称,value为节点ID</param>
        public async Task SubscribeNodesAsync(Dictionary<string, string> nodeConfigs)
        {
            if (_session == null || !_session.Connected)
            {
                throw new InvalidOperationException("OPC UA服务器未连接");
            }

            try
            {
                // 创建订阅,发布间隔100ms
                var subscription = new Subscription(_session.DefaultSubscription)
                {
                    PublishingInterval = 100,
                    PublishingEnabled = true
                };
                _session.AddSubscription(subscription);
                await subscription.CreateAsync();

                // 添加监控项
                foreach (var config in nodeConfigs)
                {
                    var monitoredItem = new MonitoredItem(subscription.DefaultItem)
                    {
                        DisplayName = config.Key,
                        StartNodeId = config.Value,
                        AttributeId = Attributes.Value,
                        SamplingInterval = 100,
                        QueueSize = 1,
                        DiscardOldest = true
                    };

                    // 绑定数据变化事件
                    monitoredItem.Notification += MonitoredItem_Notification;
                    subscription.AddItem(monitoredItem);
                    _monitoredItems.Add(config.Key, monitoredItem);
                }

                await subscription.ApplyChangesAsync();
                Console.WriteLine($"成功订阅{nodeConfigs.Count}个OPC UA节点");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"节点订阅失败:{ex}");
                throw;
            }
        }

        /// <summary>
        /// 节点数据变化通知
        /// </summary>
        private void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
        {
            if (e.NotificationValue is MonitoredItemNotification notification)
            {
                var value = notification.Value.WrappedValue.Value;
                OnDataChanged?.Invoke(monitoredItem.DisplayName, value);
            }
        }

        /// <summary>
        /// 重连后重新订阅节点
        /// </summary>
        private async Task ResubscribeNodesAsync()
        {
            if (_monitoredItems.Count == 0) return;

            var nodeConfigs = new Dictionary<string, string>();
            foreach (var item in _monitoredItems)
            {
                nodeConfigs.Add(item.Key, item.Value.StartNodeId.ToString());
            }

            _monitoredItems.Clear();
            await SubscribeNodesAsync(nodeConfigs);
        }
        #endregion

        #region 节点读写
        /// <summary>
        /// 同步写入节点值(用于设备控制)
        /// </summary>
        public async Task<StatusCode> WriteNodeValueAsync(string nodeId, object value)
        {
            if (_session == null || !_session.Connected)
            {
                return StatusCodes.BadNotConnected;
            }

            try
            {
                var writeValue = new WriteValue
                {
                    NodeId = nodeId,
                    AttributeId = Attributes.Value,
                    Value = new DataValue(new Variant(value))
                };

                var results = await _session.WriteAsync(null, new WriteValueCollection { writeValue });
                return results.Results[0];
            }
            catch (Exception ex)
            {
                Console.WriteLine($"节点写入失败:{ex}");
                return StatusCodes.BadUnexpectedError;
            }
        }
        #endregion
    }
}

4.3 国产数据库适配:TDengine时序库+达梦关系库

信创场景下,我们采用TDengine 3.x国产时序数据库存储设备历史运行数据,达梦数据库8存储设备信息、报警记录、用户权限等结构化数据,完全兼容统信UOS+鲲鹏架构。

4.3.1 TDengine时序库集成(鲲鹏ARM64适配)

TDengine是国产开源时序数据库,专门为工业物联网场景优化,写入和查询性能远超InfluxDB,原生支持鲲鹏ARM64架构。

  1. 安装TDengine .NET驱动

    dotnet add package TDengine.Connector
    
  2. 完整的TDengine数据服务实现

using TDengine.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace IndustrialControlSystem.Services
{
    public class TdengineDataService
    {
        private readonly string _connectionString;
        private readonly ITDEngineConnection _connection;

        public TdengineDataService(string host, int port, string username, string password, string database)
        {
            _connectionString = $"Host={host};Port={port};Username={username};Password={password};Database={database};Protocol=WebSocket";
            _connection = TDEngineDriver.CreateConnection(_connectionString);
            _connection.Open();
            // 初始化数据库和超级表
            InitDatabaseAndTable(database);
        }

        /// <summary>
        /// 初始化数据库和超级表(工业设备数据模型)
        /// </summary>
        private void InitDatabaseAndTable(string database)
        {
            try
            {
                // 创建数据库,保留策略365天
                var createDbSql = $"CREATE DATABASE IF NOT EXISTS {database} KEEP 365 DAYS 10 BLOCKS 4 UPDATE 1;";
                _connection.ExecuteNonQuery(createDbSql);
                // 切换数据库
                _connection.ExecuteNonQuery($"USE {database};");
                // 创建设备数据超级表
                var createSTableSql = @"
                CREATE STABLE IF NOT EXISTS device_data (
                    ts TIMESTAMP,
                    value FLOAT
                ) TAGS (
                    device_id NCHAR(50),
                    point_name NCHAR(50),
                    point_unit NCHAR(20)
                );";
                _connection.ExecuteNonQuery(createSTableSql);
                // 创建报警记录表
                var createAlarmTableSql = @"
                CREATE TABLE IF NOT EXISTS alarm_record (
                    ts TIMESTAMP,
                    device_id NCHAR(50),
                    alarm_content NCHAR(200),
                    alarm_level INT,
                    is_handle INT
                );";
                _connection.ExecuteNonQuery(createAlarmTableSql);
                Console.WriteLine("TDengine数据库初始化成功");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"TDengine初始化失败:{ex}");
            }
        }

        /// <summary>
        /// 批量写入设备实时数据
        /// </summary>
        public async Task InsertDeviceDataAsync(List<DeviceDataModel> dataList)
        {
            try
            {
                foreach (var data in dataList)
                {
                    var sql = $@"
                    INSERT INTO device_{data.DeviceId}_{data.PointName} 
                    USING device_data TAGS ('{data.DeviceId}', '{data.PointName}', '{data.PointUnit}')
                    VALUES ('{data.Ts:yyyy-MM-dd HH:mm:ss.fff}', {data.Value});
                    ";
                    await _connection.ExecuteNonQueryAsync(sql);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"TDengine数据写入失败:{ex}");
            }
        }

        /// <summary>
        /// 查询设备历史趋势数据
        /// </summary>
        public async Task<List<DeviceDataModel>> QueryTrendDataAsync(string deviceId, string pointName, DateTime startTime, DateTime endTime)
        {
            var result = new List<DeviceDataModel>();
            try
            {
                var sql = $@"
                SELECT ts, value FROM device_{deviceId}_{pointName}
                WHERE ts >= '{startTime:yyyy-MM-dd HH:mm:ss}' AND ts <= '{endTime:yyyy-MM-dd HH:mm:ss}'
                ORDER BY ts ASC;
                ";
                var reader = await _connection.ExecuteReaderAsync(sql);
                while (reader.Read())
                {
                    result.Add(new DeviceDataModel
                    {
                        Ts = reader.GetDateTime(0),
                        Value = reader.GetFloat(1),
                        DeviceId = deviceId,
                        PointName = pointName
                    });
                }
                reader.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"TDengine数据查询失败:{ex}");
            }
            return result;
        }

        /// <summary>
        /// 释放连接
        /// </summary>
        public void Dispose()
        {
            _connection?.Close();
            _connection?.Dispose();
        }
    }

    /// <summary>
    /// 设备数据模型
    /// </summary>
    public class DeviceDataModel
    {
        public DateTime Ts { get; set; }
        public string DeviceId { get; set; }
        public string PointName { get; set; }
        public string PointUnit { get; set; }
        public float Value { get; set; }
    }
}
4.3.2 达梦数据库8适配(鲲鹏ARM64)

达梦数据库是国产主流关系型数据库,完全兼容SQL Server语法,原SQL Server代码只需修改连接字符串即可直接迁移。

  1. 安装达梦数据库.NET驱动

    dotnet add package dmdbms.DmProvider
    
  2. 达梦数据库服务实现

using Dm;
using System;
using System.Data;
using System.Threading.Tasks;

namespace IndustrialControlSystem.Services
{
    public class DmDataService
    {
        private readonly string _connectionString;

        public DmDataService(string host, int port, string username, string password, string database)
        {
            _connectionString = $"Server={host};Port={port};User Id={username};Password={password};Database={database};Charset=utf8;";
        }

        /// <summary>
        /// 执行非查询SQL
        /// </summary>
        public async Task<int> ExecuteNonQueryAsync(string sql, params DmParameter[] parameters)
        {
            await using var connection = new DmConnection(_connectionString);
            await connection.OpenAsync();
            await using var command = new DmCommand(sql, connection);
            command.Parameters.AddRange(parameters);
            return await command.ExecuteNonQueryAsync();
        }

        /// <summary>
        /// 执行查询SQL,返回DataTable
        /// </summary>
        public async Task<DataTable> ExecuteQueryAsync(string sql, params DmParameter[] parameters)
        {
            await using var connection = new DmConnection(_connectionString);
            await connection.OpenAsync();
            await using var command = new DmCommand(sql, connection);
            command.Parameters.AddRange(parameters);
            var adapter = new DmDataAdapter(command);
            var dt = new DataTable();
            await Task.Run(() => adapter.Fill(dt));
            return dt;
        }

        /// <summary>
        /// 插入报警记录
        /// </summary>
        public async Task InsertAlarmRecordAsync(string deviceId, string alarmContent, int alarmLevel)
        {
            var sql = @"
            INSERT INTO alarm_record (device_id, alarm_content, alarm_level, create_time, is_handle)
            VALUES (:device_id, :alarm_content, :alarm_level, :create_time, 0);
            ";
            var parameters = new[]
            {
                new DmParameter(":device_id", deviceId),
                new DmParameter(":alarm_content", alarmContent),
                new DmParameter(":alarm_level", alarmLevel),
                new DmParameter(":create_time", DateTime.Now)
            };
            await ExecuteNonQueryAsync(sql, parameters);
        }
    }
}

4.4 .NET 10 AOT原生编译:鲲鹏架构性能优化

.NET 10支持AOT(Ahead-of-Time)原生编译,可直接把C#代码编译为鲲鹏ARM64架构的机器码,带来三大核心优势:

  1. 启动速度提升5倍以上:无需JIT编译,秒开启动,适合工业工控机开机自启场景
  2. 内存占用降低60%:无需加载.NET运行时,内存占用极低
  3. 代码防反编译:编译为原生机器码,无法被反编译,符合工业安全与信创要求
4.4.1 AOT编译配置(鲲鹏ARM64专属)

修改项目文件IndustrialControlSystem.csproj,添加AOT编译配置:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
    
    <!-- AOT编译核心配置 -->
    <PublishAot>true</PublishAot>
    <SelfContained>true</SelfContained>
    <RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
    <TrimMode>full</TrimMode>
    <PublishTrimmed>true</PublishTrimmed>
    <IlcOptimizationPreference>Speed</IlcOptimizationPreference>
    <InvariantGlobalization>false</InvariantGlobalization>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Avalonia" Version="11.1.4" />
    <PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.4" />
    <PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.4" />
    <PackageReference Include="Avalonia.Templates" Version="11.1.4" />
    <PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.0-rc2.2" />
    <PackageReference Include="Opc.Ua.Client" Version="1.5.374.1" />
    <PackageReference Include="Opc.Ua.Configuration" Version="1.5.374.1" />
    <PackageReference Include="TDengine.Connector" Version="3.2.0" />
    <PackageReference Include="dmdbms.DmProvider" Version="8.2.0.21593" />
  </ItemGroup>
</Project>
4.4.2 鲲鹏架构下的AOT发布命令
# 统信UOS+鲲鹏架构下,直接发布原生ARM64程序
dotnet publish -c Release -r linux-arm64 --self-contained true

# 发布完成后,给程序添加执行权限
chmod +x ./bin/Release/net10.0/linux-arm64/publish/IndustrialControlSystem

# 直接运行原生程序,无需安装.NET运行时
./bin/Release/net10.0/linux-arm64/publish/IndustrialControlSystem

五、国产安全层完整集成

信创验收对安全的要求非常高,必须集成完整的国产安全体系,符合等保2.0要求。

5.1 国产加密算法完整实现

.NET 10原生支持国密SM2/SM3/SM4算法,我们封装完整的工具类,用于OPC UA通信加密、密码存储、数据传输加密:

using System.Security.Cryptography;
using System.Text;

namespace IndustrialControlSystem.Utils
{
    public class SmCryptoUtils
    {
        #region SM2非对称加密(国密标准)
        /// <summary>
        /// 生成SM2密钥对
        /// </summary>
        public static (byte[] publicKey, byte[] privateKey) GenerateSm2KeyPair()
        {
            using var sm2 = SM2.Create();
            var publicKey = sm2.ExportSubjectPublicKeyInfo();
            var privateKey = sm2.ExportPkcs8PrivateKey();
            return (publicKey, privateKey);
        }

        /// <summary>
        /// SM2加密
        /// </summary>
        public static byte[] Sm2Encrypt(byte[] publicKey, byte[] data)
        {
            using var sm2 = SM2.Create();
            sm2.ImportSubjectPublicKeyInfo(publicKey, out _);
            return sm2.Encrypt(data, SM2EncryptionPadding.SM2);
        }

        /// <summary>
        /// SM2解密
        /// </summary>
        public static byte[] Sm2Decrypt(byte[] privateKey, byte[] encryptedData)
        {
            using var sm2 = SM2.Create();
            sm2.ImportPkcs8PrivateKey(privateKey, out _);
            return sm2.Decrypt(encryptedData, SM2EncryptionPadding.SM2);
        }

        /// <summary>
        /// SM2签名
        /// </summary>
        public static byte[] Sm2Sign(byte[] privateKey, byte[] data)
        {
            using var sm2 = SM2.Create();
            sm2.ImportPkcs8PrivateKey(privateKey, out _);
            return sm2.SignData(data, HashAlgorithmName.SM3);
        }
        #endregion

        #region SM3哈希算法(国密标准,替代MD5/SHA-256)
        /// <summary>
        /// SM3哈希计算
        /// </summary>
        public static byte[] Sm3Hash(byte[] data)
        {
            using var sm3 = SM3.Create();
            return sm3.ComputeHash(data);
        }

        /// <summary>
        /// 字符串SM3哈希(用于密码存储)
        /// </summary>
        public static string Sm3HashString(string text)
        {
            var data = Encoding.UTF8.GetBytes(text);
            var hash = Sm3Hash(data);
            return BitConverter.ToString(hash).Replace("-", "").ToLower();
        }
        #endregion

        #region SM4对称加密(国密标准,替代AES)
        /// <summary>
        /// 生成SM4密钥
        /// </summary>
        public static byte[] GenerateSm4Key()
        {
            using var sm4 = SM4.Create();
            sm4.GenerateKey();
            return sm4.Key;
        }

        /// <summary>
        /// SM4 CBC模式加密
        /// </summary>
        public static byte[] Sm4Encrypt(byte[] key, byte[] iv, byte[] data)
        {
            using var sm4 = SM4.Create();
            sm4.Key = key;
            sm4.IV = iv;
            sm4.Mode = CipherMode.CBC;
            sm4.Padding = PaddingMode.PKCS7;
            using var encryptor = sm4.CreateEncryptor();
            return encryptor.TransformFinalBlock(data, 0, data.Length);
        }

        /// <summary>
        /// SM4 CBC模式解密
        /// </summary>
        public static byte[] Sm4Decrypt(byte[] key, byte[] iv, byte[] encryptedData)
        {
            using var sm4 = SM4.Create();
            sm4.Key = key;
            sm4.IV = iv;
            sm4.Mode = CipherMode.CBC;
            sm4.Padding = PaddingMode.PKCS7;
            using var decryptor = sm4.CreateDecryptor();
            return decryptor.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
        }
        #endregion
    }
}

5.2 统信UOS安全配置

  1. 防火墙配置:只开放必要端口,关闭所有无关端口
    # 开放OPC UA 4840端口
    sudo ufw allow 4840/tcp
    # 开放Web远程访问80/443端口
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    # 启用防火墙
    sudo ufw enable
    # 查看防火墙状态
    sudo ufw status
    
  2. 应用权限最小化:生产环境中,给应用程序分配最小权限,禁止使用root用户运行
  3. 操作审计日志:所有用户操作、设备控制、参数修改都要记录审计日志,保存到达梦数据库,符合信创审计要求

六、信创适配避坑指南与最佳实践

6.1 鲲鹏ARM64架构专属避坑指南

  1. 绝对不要使用Windows专属API:比如System.Windows.FormsWMISystem.DirectoryServices等,这些API在Linux ARM64架构下完全不可用,必须替换为跨平台API。
  2. 文件路径大小写问题:Windows文件路径不区分大小写,统信UOS严格区分大小写,这是最常见的坑。最佳实践:所有资源文件、配置文件统一使用小写命名,代码中使用Path.Combine拼接路径,避免硬编码。
  3. ARM64架构依赖问题:部分NuGet包没有提供ARM64版本,必须替换为支持ARM64的开源替代方案。比如System.Data.SqlClient不支持ARM64,必须替换为达梦/人大金仓的国产数据库驱动。
  4. 串口/USB设备权限问题:统信UOS下访问串口、USB设备需要root权限,最佳实践是通过udev规则给设备分配普通用户权限,禁止用root运行应用。
  5. AOT编译裁剪问题:AOT编译的裁剪功能会移除未使用的代码,可能导致反射、动态绑定失效。最佳实践:在rd.xml文件中配置需要保留的类型,避免裁剪错误。

6.2 工业上位机信创改造最佳实践

  1. 渐进式迁移策略:先把原WPF项目迁移到Avalonia,在Windows环境下测试通过后,再迁移到统信UOS+鲲鹏架构,避免同时解决多个问题。
  2. 依赖注入解耦:使用.NET自带的依赖注入容器,把通信、数据存储、UI逻辑完全解耦,便于后续适配不同的国产数据库和通信协议。
  3. 全链路国产化测试:信创验收前,必须在纯国产化环境(统信UOS+鲲鹏+国产PLC+国产数据库)下进行7*24小时不间断运行测试,确保系统稳定。
  4. 离线部署方案:工业现场通常无法访问外网,必须提供离线部署包,包含.NET运行时、所有依赖库、数据库安装包,一键部署。
  5. 信创文档完善:提前准备好国产化适配说明、功能测试报告、安全审计报告、用户操作手册,便于快速通过信创验收。

七、总结

国产化工控机浪潮已经不可逆转,C# + .NET 10 + Avalonia 这套技术栈,完美解决了工业上位机国产化改造的核心痛点:原WPF项目可零代码重构UI,原生支持统信UOS+鲲鹏ARM64架构,工业生态完善,性能媲美Windows环境,同时完全符合信创安全要求。

本文完整覆盖了从环境搭建、核心模块实现、国产安全集成到AOT编译优化的全流程,所有代码均可直接在统信UOS+鲲鹏架构下运行,帮你快速完成工业上位机的国产化改造,顺利通过信创验收。

Logo

一站式 AI 云服务平台

更多推荐