Files
ExperionCrawler/fastTable/project_fasttable_bugs.md
2026-04-30 08:16:21 +09:00

7.8 KiB

name, description, type, originSessionId
name description type originSessionId
fastTable/fastRecord 구현 검증 결과 roo-fasttable-implementation.md 계획 대비 실제 구현 차이 및 버그 목록 project ec4d397a-b394-4d23-b041-03b70a7d0136

검증 대상

  • 계획서: plans/roo-fasttable-implementation.md
  • 검증 시점: 2026-04-29
  • 빌드 결과: 경고 0건, 에러 0건 (빌드는 통과)

Step별 구현 상태

Step 파일 상태 비고
1 ExperionEntities.cs 완료
2 IExperionServices.cs 완료
3 IExperionServices.cs 완료
4 ExperionDbContext.cs 완료
5 ExperionDbContext.cs (DDL) ⚠️ 차이 tag_list JSONB (계획: TEXT). EnsureCreatedAsync가 먼저 실행되어 실제로는 text 타입이 됨 → 런타임 문제 없음
6 ExperionDbContext.cs (메서드) ⚠️ 차이 GetFastSessionsAsync 정렬 역전, UpdateFastSessionRowCountAsync 구현 방식 다름
7 ExperionDbContext.cs (메서드) 버그 CSV Export 헤더 오류
8~11 ExperionFastService.cs 치명적 StartSessionAsync 항상 실패
12 ExperionFastCleanupService 완료 ExperionFastService.cs 파일에 같이 위치 (문제없음)
13 ExperionControllers.cs 완료 FastPinRequest → PinRequest로 이름 다름 (동작 동일)
14 Program.cs 완료 DI 패턴 3줄 패턴 정확히 구현
15 appsettings.json 완료 Fast 섹션 추가됨
16 index.html 완료 Bootstrap 방식으로 구현 (계획과 스타일 다르나 기능 동일)
17 app.js ⚠️ 차이 태그 목록 로딩 방식 다름 (전역변수 의존)
18 style.css ⚠️ 생략 Bootstrap 사용하여 별도 CSS 불필요

버그 목록

Bug 1 — StartSessionAsync 항상 실패 (치명적)

파일: src/Infrastructure/OpcUa/ExperionFastService.cs:99-116

현재 코드:

var cfg = await _configProvider.GetConfigAsync(new ExperionServerConfig()); // 빈 설정!
if (string.IsNullOrEmpty(cfg?.ServerConfiguration?.BaseAddresses?.Count > 0 ? cfg.ServerConfiguration.BaseAddresses[0] : null))
    throw new InvalidOperationException("서버 엔드포인트 URL이 설정되어 있지 않습니다.");

if (!await _opcClient.IsConnectedAsync(cfg))
    throw new InvalidOperationException("OPC UA 서버에 연결되어 있지 않습니다.");

// 노드 유효성 사전 검증
foreach (var tagName in request.TagList)
{
    var nodeId = await db.GetNodeIdByTagNameAsync(tagName);
    if (string.IsNullOrEmpty(nodeId))
        throw new ArgumentException($"태그 '{tagName}'의 nodeId를 찾을 수 없습니다.");

    var readResult = await _opcClient.ReadTagAsync(new ExperionServerConfig(), nodeId); // 빈 설정!
    if (!readResult.Success)
        throw new ArgumentException($"태그 '{tagName}' 읽기 실패: {readResult.ErrorMessage}");
}

문제: new ExperionServerConfig()는 ServerHostName이 빈 문자열이라 EndpointUrl = opc.tcp://:4840. ApplicationConfiguration의 BaseAddresses가 비어 있어 "서버 엔드포인트 URL이 설정되어 있지 않습니다." 항상 throw.

또한 StartSubscriptionAsync(ctx, cfg) 내부:

var endpoint = await SelectEndpointAsync(cfg, cfg.ServerConfiguration?.BaseAddresses?[0] ?? string.Empty);
var session = await CreateSessionAsync(cfg, endpoint, new ExperionServerConfig()); // 빈 UserName/Password

수정 방법 (계획서 Step 9 참조): realtime_autostart.json에서 ExperionServerConfig를 읽어야 함.

private static readonly string RealtimeFlagPath = Path.GetFullPath("realtime_autostart.json");

private static async Task<ExperionServerConfig?> ReadServerConfigAsync()
{
    if (!File.Exists(RealtimeFlagPath)) return null;
    try
    {
        var json = await File.ReadAllTextAsync(RealtimeFlagPath);
        return JsonSerializer.Deserialize<ExperionServerConfig>(json);
    }
    catch { return null; }
}

그리고 StartSessionAsync 시작 부분을:

var serverCfg = await ReadServerConfigAsync();
if (serverCfg == null)
    throw new InvalidOperationException("OPC UA 서버 설정을 찾을 수 없습니다. 실시간 구독을 먼저 시작하세요.");

var appConfig = await _configProvider.GetConfigAsync(serverCfg);
// IsConnectedAsync 체크 제거 or appConfig 사용

StartSubscriptionAsync 시그니처도 변경:

private async Task StartSubscriptionAsync(FastSessionContext ctx, ExperionServerConfig serverCfg)
{
    var appConfig = await _configProvider.GetConfigAsync(serverCfg);
    var endpoint  = await SelectEndpointAsync(appConfig, serverCfg.EndpointUrl);
    var identity  = new UserIdentity(serverCfg.UserName, System.Text.Encoding.UTF8.GetBytes(serverCfg.Password));
    // ...
}

IExperionOpcClient 생성자 주입도 제거 가능 (사전 검증 로직 삭제). Program.cs의 DI 등록은 이미 정상.


Bug 2 — CSV Export 헤더 오류

파일: src/Infrastructure/Database/ExperionDbContext.cs:828-829

현재 코드 (잘못됨):

csv.WriteRecord(new { RecordedAt = "recorded_at", TagNames = tagNames.Select((t, i) => $"tag{i+1}") });
await writer.WriteLineAsync();

→ CSV 헤더가 RecordedAt,TagNames 또는 recorded_at,tag1 형태로 출력됨. 태그명이 아님.

수정 방법 (계획서 Step 7 참조):

using var writer = new StreamWriter(stream, leaveOpen: true);
await writer.WriteLineAsync("recorded_at," + string.Join(",", tagNames));

foreach (var g in records.GroupBy(x => x.RecordedAt).OrderBy(g => g.Key))
{
    var values = g.ToDictionary(r => r.TagName, r => r.Value);
    var row = g.Key.ToString("o") + "," +
              string.Join(",", tagNames.Select(t => values.TryGetValue(t, out var v) ? $"\"{v}\"" : ""));
    await writer.WriteLineAsync(row);
}
await writer.FlushAsync();

CsvHelper 의존성 제거. CsvHelper using도 제거 필요.


Bug 3 — 세션 목록 정렬 역전 (경미)

파일: src/Infrastructure/Database/ExperionDbContext.cs:760

현재:

.OrderBy(x => x.StartedAt)  // 오래된 것이 위

계획:

.OrderByDescending(x => x.StartedAt)  // 최신이 위

Bug 4 — 신규 세션 모달에서 태그 목록이 비어 있을 수 있음 (경미)

파일: src/Web/wwwroot/js/app.js, btn-fast-new 이벤트 핸들러

현재 코드:

(typeof tagNames !== 'undefined' ? tagNames : []).forEach(name => { ... });

tagNames 전역 변수가 실시간 탭 방문 전에는 비어 있음 → 태그 선택 불가.

계획서 fastNewModal() 참조 → /api/realtime/points 직접 fetch:

async function fastNewModal() {
    const res = await fetch('/api/realtime/points');
    const select = document.getElementById('fast-tag-select');
    select.innerHTML = '';
    if (res.ok) {
        const data = await res.json();
        (data.items || []).forEach(p => {
            const opt = document.createElement('option');
            opt.value = p.tagName || p.TagName;
            opt.textContent = p.tagName || p.TagName;
            select.appendChild(opt);
        });
    }
    // ...
}

수정 우선순위

  1. Bug 1 (치명적): StartSessionAsync — realtime_autostart.json 기반 서버 설정 읽기로 전면 수정
  2. Bug 2 (중요): ExportFastRecordsToCsvAsync — CsvHelper 제거, 수동 CSV 작성으로 교체
  3. Bug 3 (경미): GetFastSessionsAsync 정렬 방향 수정
  4. Bug 4 (경미): fastNewModal 태그 목록 직접 fetch로 수정

Why: Bug 1은 fastRecord 기능이 전혀 동작하지 않게 만드는 근본 원인. realtime_autostart.json에 OPC UA 서버 접속 정보가 있음 (실시간 구독 시작 시 저장됨). How to apply: Roo에게 위 4개 버그를 순서대로 수정 지시. 각 수정 후 dotnet build 확인.