opencode 로 바꾸고 작업전 커밋
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using ExperionCrawler.Core.Application.Interfaces;
|
||||
using ExperionCrawler.Core.Domain.Entities;
|
||||
using ExperionCrawler.Infrastructure.Database;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
|
||||
namespace ExperionCrawler.Infrastructure.OpcUa;
|
||||
|
||||
/// <summary>
|
||||
/// 메타데이터(desc, area, state0~7descriptor)를 OPC UA에서 읽어서 tag_metadata 테이블에 저장/갱신
|
||||
/// </summary>
|
||||
public class MetadataLoaderService : IMetadataLoaderService
|
||||
{
|
||||
private readonly IExperionOpcClient _opcClient;
|
||||
private readonly ExperionDbContext _ctx;
|
||||
private readonly ILogger<MetadataLoaderService> _logger;
|
||||
|
||||
// 로드할 메타데이터 속성 목록
|
||||
private static readonly string[] MetaAttributes =
|
||||
{
|
||||
"desc", "area",
|
||||
"state0descriptor", "state1descriptor", "state2descriptor",
|
||||
"state3descriptor", "state4descriptor", "state5descriptor",
|
||||
"state6descriptor", "state7descriptor"
|
||||
};
|
||||
|
||||
public MetadataLoaderService(
|
||||
IExperionOpcClient opcClient,
|
||||
ExperionDbContext ctx,
|
||||
ILogger<MetadataLoaderService> logger)
|
||||
{
|
||||
_opcClient = opcClient;
|
||||
_ctx = ctx;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<int> LoadMetadataAsync(ExperionServerConfig cfg, IEnumerable<string> baseTags)
|
||||
{
|
||||
var baseTagList = baseTags.ToList(); // 이중 열거 방지
|
||||
|
||||
// ── Step 1: 모든 노드 ID 수집 ──────────────────────────────────────
|
||||
var nodeMap = new Dictionary<string, (string baseTag, string attr)>();
|
||||
foreach (var baseTag in baseTagList)
|
||||
{
|
||||
foreach (var attr in MetaAttributes)
|
||||
{
|
||||
var nodeId = $"{cfg.ServerHostName}:{baseTag}.{attr}";
|
||||
var fullNodeId = $"ns=1;s={nodeId}";
|
||||
nodeMap[fullNodeId] = (baseTag.ToLowerInvariant(), attr);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 2: 배치 읽기 (ReadTagsAsync 사용) ────────────────────────
|
||||
var results = await _opcClient.ReadTagsAsync(cfg, nodeMap.Keys);
|
||||
var entries = new List<(string baseTag, string attr, string? value, string nodeId)>();
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result.Success && result.Value != null && nodeMap.TryGetValue(result.NodeId, out var meta))
|
||||
{
|
||||
entries.Add((meta.baseTag, meta.attr, result.Value?.ToString(), result.NodeId));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 3: 단일 배치 UPSERT ──────────────────────────────────────
|
||||
if (entries.Count > 0)
|
||||
{
|
||||
// VALUES 절을 동적으로 생성하여 한 번에 INSERT
|
||||
// CTE 컬럼(5개)과 VALUES 값(5개) 일치: base_tag, attribute, value, node_id, loaded_at
|
||||
var valuesSql = string.Join(", ", entries.Select((e, i) =>
|
||||
$"(@bt{i}, @attr{i}, @val{i}, @nid{i}, NOW())"));
|
||||
|
||||
await _ctx.Database.ExecuteSqlRawAsync(@"
|
||||
WITH new_data (base_tag, attribute, value, node_id, loaded_at) AS (
|
||||
VALUES " + valuesSql + @"
|
||||
)
|
||||
INSERT INTO tag_metadata (base_tag, attribute, value, node_id, loaded_at)
|
||||
SELECT base_tag, attribute, value, node_id, loaded_at FROM new_data
|
||||
ON CONFLICT (base_tag, attribute)
|
||||
DO UPDATE SET value = excluded.value, node_id = excluded.node_id, loaded_at = NOW()",
|
||||
entries.SelectMany((e, i) => new object[] {
|
||||
new NpgsqlParameter($"@bt{i}", e.baseTag),
|
||||
new NpgsqlParameter($"@attr{i}", e.attr),
|
||||
new NpgsqlParameter($"@val{i}", (object?)e.value ?? DBNull.Value),
|
||||
new NpgsqlParameter($"@nid{i}", e.nodeId)
|
||||
}).ToArray());
|
||||
}
|
||||
|
||||
// v_tag_summary는 일반 VIEW이므로 REFRESH 불필요 (조회 시 실시간 JOIN)
|
||||
_logger.LogInformation("[Metadata] 로드 완료: {Count}개 속성 ({TagCount}개 태그)", entries.Count, baseTagList.Count);
|
||||
return entries.Count;
|
||||
}
|
||||
|
||||
public async Task<int> ReloadMetadataAsync(ExperionServerConfig cfg, IEnumerable<string>? baseTags = null)
|
||||
{
|
||||
// baseTags가 null이면 tag_metadata에서 전체 base_tag 조회
|
||||
var tags = baseTags?.ToList() ?? await _ctx.TagMetadata.Select(t => t.BaseTag).Distinct().ToListAsync();
|
||||
return await LoadMetadataAsync(cfg, tags);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user