本文基于企业级生产环境需求,全面重构 OPC UA 与 ABP vNext 集成框架,涵盖:
实现「即克隆、即运行、即监控」的工业数据平台!✨
appsettings.json
"OpcUa": {
"Endpoint": "opc.tcp://localhost:4840",
"NodeIds": ["ns=2;s=Device1", "ns=2;s=Device2"],
"CacheDurationSeconds": 120,
"AutoAcceptUntrusted": false,
"Certificate": {
"StorePath": "/etc/opcua/certs",
"SubjectName": "CN=OpcAbpIntegration"
}
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var config = context.ServiceProvider.GetRequiredService<IConfiguration>();
var section = config.GetSection("OpcUa");
if (!section.Exists())
throw new ConfigurationErrorsException(" OpcUa 配置节缺失!");
var endpoint = section["Endpoint"];
if (string.IsNullOrWhiteSpace(endpoint))
throw new ConfigurationErrorsException(" OpcUa.Endpoint 不能为空!");
var nodeIds = section.GetSection("NodeIds").Get<string[]>();
if (nodeIds == null || nodeIds.Length == 0)
throw new ConfigurationErrorsException(" OpcUa.NodeIds 至少配置一个!");
}
public class OpcUaService : IOpcUaService, ISingletonDependency
{
private readonly IOptions<OpcUaOptions> _options;
private readonly ILogger<OpcUaService> _logger;
private Session? _session;
private readonly SemaphoreSlim _lock = new(1, 1);
public OpcUaService(IOptions<OpcUaOptions> options, ILogger<OpcUaService> logger)
{
_options = options;
_logger = logger;
}
public async Task<Session> EnsureSessionAsync()
{
await _lock.WaitAsync();
try
{
if (_session?.Connected == true)
return _session;
var config = new ApplicationConfiguration
{
ApplicationName = "OpcAbpIntegration",
ApplicationUri = "urn:abp:opcua",
ApplicationType = ApplicationType.Client,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier
{
StoreType = "Directory",
StorePath = _options.Value.Certificate.StorePath,
SubjectName = _options.Value.Certificate.SubjectName
},
AutoAcceptUntrustedCertificates = _options.Value.AutoAcceptUntrusted
},
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },
TransportQuotas = new TransportQuotas
{ OperationTimeout = 15000,
MaxMessageSize = 4_194_304 }
};
await config.Validate(ApplicationType.Client);
var endpointDesc = CoreClientUtils.SelectEndpoint(_options.Value.Endpoint, false);
var endpoint = new ConfiguredEndpoint(null, endpointDesc, EndpointConfiguration.Create(config));
_session = await Session.Create(
config, endpoint, false, "OPC UA", 60000, new UserIdentity(), null);
_logger.LogInformation("✅ OPC UA 会话已连接:{Endpoint}", _options.Value.Endpoint);
return _session;
}
finally
{
_lock.Release();
}
}
public async Task<string> ReadNodeAsync(string nodeId)
{
return await Policy
.Handle<Exception>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt => TimeSpan.FromSeconds(1 << attempt),
onRetry: (ex, delay) => _logger.LogWarning(ex, "重试读取节点 {NodeId}", nodeId)
)
.ExecuteAsync(async () =>
{
var session = await EnsureSessionAsync();
_logger.LogDebug(" 读取节点 {NodeId}", nodeId);
var node = new ReadValueId { NodeId = new NodeId(nodeId), AttributeId = Attributes.Value };
var results = new DataValueCollection();
await session.Read(null, 0, TimestampsToReturn.Both, new[] { node }, out results, out _);
return results.FirstOrDefault()?.Value?.ToString() ?? "";
});
}
}
public class OpcUaCollectorWorker : BackgroundWorkerBase
{
private readonly IOpcUaService _opcUa;
private readonly IDistributedCache<MyDeviceCacheItem> _cache;
private readonly IMyDeviceRepository _repository;
private readonly IDistributedEventBus _eventBus;
private readonly IOptions<OpcUaOptions> _options;
private readonly ILogger<OpcUaCollectorWorker> _logger;
public override float DelayFactor => 1; // 可配置执行间隔
public OpcUaCollectorWorker(
IOpcUaService opcUa,
IDistributedCache<MyDeviceCacheItem> cache,
IMyDeviceRepository repository,
IDistributedEventBus eventBus,
IOptions<OpcUaOptions> options,
ILogger<OpcUaCollectorWorker> logger)
{
_opcUa = opcUa;
_cache = cache;
_repository = repository;
_eventBus = eventBus;
_options = options;
_logger = logger;
}
[UnitOfWork]
public override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var failedNodes = new List<string>();
var sw = Stopwatch.StartNew();
foreach (var nodeId in _options.Value.NodeIds)
{
try
{
var value = await _opcUa.ReadNodeAsync(nodeId);
await _repository.InsertOrUpdateAsync(
new MyDeviceData(nodeId, value),
existing => existing.Update(value)
);
await _cache.SetAsync(
nodeId,
new MyDeviceCacheItem(value),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(_options.Value.CacheDurationSeconds)
});
_logger.LogInformation(" 节点 {NodeId} 数据已更新", nodeId);
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ 读取节点 {NodeId} 失败", nodeId);
failedNodes.Add(nodeId);
}
}
sw.Stop();
_logger.LogInformation(" 本次采集用时 {Elapsed} ms", sw.ElapsedMilliseconds);
if (failedNodes.Count > 0)
{
await _eventBus.PublishAsync(new NodeReadFailedEvent(failedNodes));
_logger.LogWarning("⚠️ 发布读取失败告警,节点:{Nodes}", string.Join(',', failedNodes));
}
}
}
[DependsOn(
typeof(AbpEntityFrameworkCoreModule),
typeof(AbpDistributedCacheModule),
typeof(AbpBackgroundWorkersModule),
typeof(AbpAutofacModule))]
public class OpcUaModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 1️⃣ 配置绑定与校验(见 OnApplicationInitialization)
context.Services.Configure<OpcUaOptions>(
context.Services.GetConfiguration().GetSection("OpcUa"));
// 2️⃣ 核心服务注册
context.Services.AddSingleton<IOpcUaService, OpcUaService>();
// 3️⃣ EF Core & 仓储
context.Services.AddAbpDbContext<MyDbContext>(opts =>
{
opts.AddDefaultRepositories(includeAllEntities: true);
});
// 4️⃣ Background Worker
context.Services.AddBackgroundWorker<OpcUaCollectorWorker>();
// 5️⃣ 健康检查
context.Services.AddHealthChecks()
.AddCheck<OpcUaHealthCheck>("opcua")
.AddNpgSql("YourPostgreConnection")
.AddRedis("localhost");
// 6️⃣ OpenTelemetry 跟踪
context.Services.AddOpenTelemetryTracing(builder =>
{
builder.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource("OpcUaService")
.AddJaegerExporter();
});
}
}
OpcUaHealthCheck
示例public class OpcUaHealthCheck : IHealthCheck
{
private readonly IOpcUaService _opcUa;
public OpcUaHealthCheck(IOpcUaService opcUa) => _opcUa = opcUa;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken token = default)
{
try
{
await _opcUa.EnsureSessionAsync();
return HealthCheckResult.Healthy("OPC UA session is healthy");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("OPC UA session failed", ex);
}
}
}
openssl genrsa -out client.key 2048
openssl req -new -x509 -key client.key -out client.crt -days 365 \
-subj "/CN=OpcAbpIntegration"
mkdir -p /etc/opcua/certs/trusted
cp client.crt /etc/opcua/certs/trusted/
apiVersion: v1
kind: Secret
metadata:
name: opcua-certs
namespace: your-ns
stringData:
client.crt: |
-----BEGIN CERTIFICATE-----
...base64...
-----END CERTIFICATE-----
volumeMounts:
- name: opcua-certs
mountPath: /etc/opcua/certs
volumes:
- name: opcua-certs
secret:
secretName: opcua-certs
readinessProbe:
httpGet:
path: /health/ready
port: 5000
initialDelaySeconds: 10
periodSeconds: 30
livenessProbe:
httpGet:
path: /health/live
port: 5000
initialDelaySeconds: 30
periodSeconds: 60
OpenTelemetry.Extensions.Hosting
OpenTelemetry.Instrumentation.Http
, AspNetCore
, EntityFrameworkCore
此版本已实现企业级「高可用、可复现、可维护」规范,覆盖从 证书、配置、作业调度、缓存优化、健康检查 到 可观测 的全链路实践。
推荐 将此框架部署于 IoT Edge、Kubernetes,并结合 CI/CD 与 自动化证书脚本,打造工业物联网的实时采集+可视化体系!