国产化工控机浪潮下:C# + .NET 10 实现统信UOS+鲲鹏架构全兼容,零代码重构工业上位机
摘要:面对工业自动化领域“信创国产化”需求,本文提出使用C# + .NET 10 + Avalonia方案,将原有Windows WPF工业上位机零代码重构到统信UOS+鲲鹏架构。文章详细介绍了四层系统架构设计,对比主流技术栈优势,并提供统信UOS环境搭建全流程,包括鲲鹏ARM64专属配置、.NET 10安装及开发工具部署。该方案能保留原有功能与UI风格,实现工业上位机的全栈国产化快速迁移,满足信
做工业自动化的朋友,最近是不是被“信创国产化”的要求追着跑?手里的Windows工控机+西门子/欧姆龙PLC+WPF上位机用得好好的,但客户突然要求换成统信UOS服务器/桌面版+鲲鹏920/930处理器的国产化工控机,还要保留所有功能——实时监控、OPC UA通信、历史数据存储、数据分析,甚至连原来的UI风格都不能变?
别慌,这事儿真没你想的那么难!今天咱们就用C# + .NET 10,把原来的Windows WPF工业上位机,零代码(或者说极少代码)重构到统信UOS+鲲鹏架构上,配套完整的可运行代码、架构流程图、信创适配踩坑指南,帮你快速通过信创验收,实现工业上位机的全栈国产化。
一、先看整体架构设计
很多人做信创工业上位机就是随便换个Linux系统,把原来的WPF代码改成MAUI或者Avalonia,完全没有架构设计,这也是导致后期性能差、兼容性差、维护难的核心原因。先给你看一张统信UOS+鲲鹏架构下的全兼容工业上位机架构图,这套架构完全符合信创要求,同时实现了Windows到国产系统的无缝迁移:
从图里能看出来,整个系统分为四层,咱们今天重点讲国产操作系统层适配、.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 环境搭建流程图
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,在扩展市场安装以下核心扩展:
- C# Dev Kit:微软官方C#开发扩展,提供智能提示、调试、代码重构
- Avalonia for VS Code:Avalonia官方扩展,提供XAML实时预览、智能提示
- NuGet Package Manager:NuGet包管理工具
- TDengine:TDengine时序库连接与查询工具
- 达梦数据库扩展:达梦数据库连接工具(可选)
四、.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迁移步骤
- 创建Avalonia MVVM项目:
dotnet new avalonia.mvvm -n IndustrialControlSystem cd IndustrialControlSystem - 迁移WPF资源与样式:把原WPF项目的
Styles、Images、字体等资源直接复制到Assets文件夹,Avalonia完全支持WPF的样式语法、资源字典、数据模板。 - 迁移XAML视图:把原WPF的
Window、UserControl复制到Views文件夹,仅需修改2处核心内容:- 把XAML头部的命名空间
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"替换为xmlns="https://github.com/avaloniaui" - 把
Window标签替换为Window(Avalonia的Window标签),UserControl标签无需修改
- 把XAML头部的命名空间
- 迁移ViewModel代码:把原WPF的ViewModel代码直接复制到
ViewModels文件夹,Avalonia完全支持INotifyPropertyChanged、ICommand等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+鲲鹏架构:
- LiveCharts2:开源跨平台图表库,支持实时趋势图、柱状图、饼图,与WPF的LiveCharts用法完全一致
dotnet add package LiveChartsCore.SkiaSharpView.Avalonia - AvaloniaGauges:开源仪表盘控件库,支持圆形、线性仪表盘,完全复刻工业场景的仪表样式
- Avalonia.Controls.DataGrid:官方数据表格控件,与WPF DataGrid用法100%一致
- 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架构。
-
安装TDengine .NET驱动
dotnet add package TDengine.Connector -
完整的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代码只需修改连接字符串即可直接迁移。
-
安装达梦数据库.NET驱动
dotnet add package dmdbms.DmProvider -
达梦数据库服务实现
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架构的机器码,带来三大核心优势:
- 启动速度提升5倍以上:无需JIT编译,秒开启动,适合工业工控机开机自启场景
- 内存占用降低60%:无需加载.NET运行时,内存占用极低
- 代码防反编译:编译为原生机器码,无法被反编译,符合工业安全与信创要求
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安全配置
- 防火墙配置:只开放必要端口,关闭所有无关端口
# 开放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 - 应用权限最小化:生产环境中,给应用程序分配最小权限,禁止使用root用户运行
- 操作审计日志:所有用户操作、设备控制、参数修改都要记录审计日志,保存到达梦数据库,符合信创审计要求
六、信创适配避坑指南与最佳实践
6.1 鲲鹏ARM64架构专属避坑指南
- 绝对不要使用Windows专属API:比如
System.Windows.Forms、WMI、System.DirectoryServices等,这些API在Linux ARM64架构下完全不可用,必须替换为跨平台API。 - 文件路径大小写问题:Windows文件路径不区分大小写,统信UOS严格区分大小写,这是最常见的坑。最佳实践:所有资源文件、配置文件统一使用小写命名,代码中使用
Path.Combine拼接路径,避免硬编码。 - ARM64架构依赖问题:部分NuGet包没有提供ARM64版本,必须替换为支持ARM64的开源替代方案。比如
System.Data.SqlClient不支持ARM64,必须替换为达梦/人大金仓的国产数据库驱动。 - 串口/USB设备权限问题:统信UOS下访问串口、USB设备需要root权限,最佳实践是通过udev规则给设备分配普通用户权限,禁止用root运行应用。
- AOT编译裁剪问题:AOT编译的裁剪功能会移除未使用的代码,可能导致反射、动态绑定失效。最佳实践:在
rd.xml文件中配置需要保留的类型,避免裁剪错误。
6.2 工业上位机信创改造最佳实践
- 渐进式迁移策略:先把原WPF项目迁移到Avalonia,在Windows环境下测试通过后,再迁移到统信UOS+鲲鹏架构,避免同时解决多个问题。
- 依赖注入解耦:使用.NET自带的依赖注入容器,把通信、数据存储、UI逻辑完全解耦,便于后续适配不同的国产数据库和通信协议。
- 全链路国产化测试:信创验收前,必须在纯国产化环境(统信UOS+鲲鹏+国产PLC+国产数据库)下进行7*24小时不间断运行测试,确保系统稳定。
- 离线部署方案:工业现场通常无法访问外网,必须提供离线部署包,包含.NET运行时、所有依赖库、数据库安装包,一键部署。
- 信创文档完善:提前准备好国产化适配说明、功能测试报告、安全审计报告、用户操作手册,便于快速通过信创验收。
七、总结
国产化工控机浪潮已经不可逆转,C# + .NET 10 + Avalonia 这套技术栈,完美解决了工业上位机国产化改造的核心痛点:原WPF项目可零代码重构UI,原生支持统信UOS+鲲鹏ARM64架构,工业生态完善,性能媲美Windows环境,同时完全符合信创安全要求。
本文完整覆盖了从环境搭建、核心模块实现、国产安全集成到AOT编译优化的全流程,所有代码均可直接在统信UOS+鲲鹏架构下运行,帮你快速完成工业上位机的国产化改造,顺利通过信创验收。
更多推荐


所有评论(0)