운전시 버그 수정
This commit is contained in:
148
CLAUDE.md
148
CLAUDE.md
@@ -7,6 +7,133 @@
|
||||
|
||||
## 완료된 작업
|
||||
|
||||
### 로그 정리 — 스냅샷 로그 2줄 → 1줄 (2026-04-15)
|
||||
|
||||
#### 증상
|
||||
히스토리 스냅샷 1회 저장마다 터미널에 로그 2줄 출력:
|
||||
```
|
||||
[ExperionDb] history 스냅샷: 1752건 @ 01:14:18
|
||||
[HistoryService] 스냅샷 저장: 1752건
|
||||
```
|
||||
|
||||
#### 원인
|
||||
DB 저장 완료 후 `ExperionDbService`에서 `LogInformation`, 호출자 `ExperionHistoryService`에서도 `LogInformation`. 저장은 1회이나 로그가 2줄.
|
||||
|
||||
#### 수정 파일
|
||||
|
||||
| 파일 | 수정 내용 |
|
||||
|------|----------|
|
||||
| `src/Infrastructure/Database/ExperionDbContext.cs` | `SnapshotToHistoryAsync()` 내부 로그를 `LogInformation` → `LogDebug`로 변경 |
|
||||
|
||||
#### 결과
|
||||
운영 로그(`Information` 레벨)에서 `[HistoryService] 스냅샷 저장: N건` 1줄만 출력.
|
||||
|
||||
---
|
||||
|
||||
### 버그 수정 — Ctrl+C 종료 시 자동재시작 플래그 삭제 오류 (2026-04-15)
|
||||
|
||||
#### 증상
|
||||
Ctrl+C로 앱 종료 시 `realtime_autostart.json` 플래그 파일이 삭제되어, 재기동 후 자동 구독 시작이 동작하지 않음.
|
||||
|
||||
#### 원인
|
||||
`IHostedService.StopAsync(CancellationToken)` (앱 종료 훅)이 UI 수동 중지 메서드인 `StopAsync()`를 그대로 호출. `StopAsync()`는 플래그 파일을 삭제하므로 앱 종료와 수동 중지를 구분하지 못했음.
|
||||
|
||||
#### 수정 파일
|
||||
|
||||
| 파일 | 수정 내용 |
|
||||
|------|----------|
|
||||
| `src/Infrastructure/OpcUa/ExperionRealtimeService.cs` | `IHostedService.StopAsync(CancellationToken)` 분리 — `_cts.Cancel()` + 태스크 대기만 수행, 플래그 파일 삭제 없음 |
|
||||
|
||||
#### 동작 구분
|
||||
|
||||
| 종료 방식 | 플래그 파일 |
|
||||
|----------|------------|
|
||||
| Ctrl+C (앱 종료) | **유지** → 재기동 시 자동 구독 시작 |
|
||||
| UI 중지 버튼 | **삭제** → 재기동 후 자동 시작 없음 |
|
||||
|
||||
---
|
||||
|
||||
### 버그 수정 — 이력 조회 중복 키 예외 (2026-04-15)
|
||||
|
||||
#### 증상
|
||||
이력 조회 시 서버 500 에러:
|
||||
```
|
||||
System.ArgumentException: An item with the same key has already been added.
|
||||
Key: p-6102.hzset.fieldvalue
|
||||
at ExperionDbService.QueryHistoryAsync ... line 342
|
||||
```
|
||||
|
||||
#### 원인
|
||||
`history_table`에 동일 `recorded_at` + 동일 `tagname` 조합이 중복 저장된 행 존재. `.ToDictionary(r => r.TagName, r => r.Value)` 호출 시 중복 키로 예외 발생.
|
||||
|
||||
#### 수정 파일
|
||||
|
||||
| 파일 | 수정 내용 |
|
||||
|------|----------|
|
||||
| `src/Infrastructure/Database/ExperionDbContext.cs` | `TagName` 기준 `GroupBy` 추가 → 중복 시 `.Last().Value` 사용 |
|
||||
|
||||
---
|
||||
|
||||
### 기능 추가 — 이력 조회 날짜/시간 팝업 피커 (2026-04-15)
|
||||
|
||||
#### 배경
|
||||
- `datetime-local` 입력이 Windows 브라우저 로케일에 따라 AM/PM 12시간제로 표시됨
|
||||
- 서버(Ubuntu UTC) / 브라우저(Windows KST) 시간대 차이로 인한 표시 혼란
|
||||
|
||||
#### 설계
|
||||
- `datetime-local` 입력 제거 → 클릭 시 커스텀 달력+시간 팝업 오픈
|
||||
- 달력: 월 이동 가능, 오늘 날짜 amber 강조, 선택일 반전 표시
|
||||
- 시간: 24시간제, `−`/`+` 버튼 또는 직접 입력 (0–23시, 0–59분)
|
||||
- 확인 시 `YYYY-MM-DD HH:MM` 형식으로 필드 표시
|
||||
- hidden input에 로컬 시간 문자열 저장 → `new Date(...).toISOString()`으로 KST→UTC 변환 후 서버 전송 (기존 로직 유지)
|
||||
|
||||
#### 수정 파일
|
||||
|
||||
| 파일 | 수정 내용 |
|
||||
|------|----------|
|
||||
| `src/Web/wwwroot/index.html` | `datetime-local` 2개 → `.dt-display` + `hidden input` 교체; 팝업 HTML(`#dt-popup`, `#dt-overlay`) 추가 |
|
||||
| `src/Web/wwwroot/css/style.css` | `.dt-popup`, `.dt-cal-grid`, `.dt-day`, `.dt-time-row` 등 피커 전용 다크 테마 스타일 추가; 기존 `datetime-local` AM/PM 숨김 CSS 제거 |
|
||||
| `src/Web/wwwroot/js/app.js` | `dtOpen()`, `dtRenderCal()`, `dtSelectDay()`, `dtPrevMonth()`, `dtNextMonth()`, `dtAdjTime()`, `dtClampTime()`, `dtConfirm()`, `dtClear()`, `dtClose()` 구현; `histReset()`에서 `dtClearField()` 호출로 표시 텍스트 초기화 |
|
||||
|
||||
#### 빌드 결과
|
||||
- 경고 8건 (기존 동일), **에러 0건** — 빌드 성공
|
||||
|
||||
---
|
||||
|
||||
### 버그 수정 — 단일 태그 읽기 성공/실패 판정 오류 (2026-04-15)
|
||||
|
||||
#### 증상
|
||||
서버접속테스트 페이지에서 단일 태그 읽기 시, OPC UA 서버가 `BadNodeIdUnknown(0x80340000)` 등 에러 상태 코드를 반환해도 "✅ 읽기 성공"으로 표시되는 버그.
|
||||
|
||||
#### 원인
|
||||
`ExperionOpcClient.cs`의 `ReadTagsAsync` 내부에서 `StatusCode` 값과 무관하게 `Success = true`를 하드코딩해서 `ExperionReadResult`를 생성했음.
|
||||
|
||||
#### 수정 파일
|
||||
|
||||
| 파일 | 수정 내용 |
|
||||
|------|----------|
|
||||
| `src/Infrastructure/OpcUa/ExperionOpcClient.cs` | `StatusCode.IsGood()` 결과를 `Success` 플래그로 사용. Bad이면 `Success=false`, `Value=null`, `Error`에 상태 코드 메시지 설정 |
|
||||
|
||||
#### 결과
|
||||
`BadNodeIdUnknown` 등 Bad 상태 코드 수신 시 → ❌ 읽기 실패로 정상 표시
|
||||
|
||||
#### 빌드 결과 (경고 상세)
|
||||
경고 8건, **에러 0건** — 빌드 성공
|
||||
|
||||
| # | 파일 | 내용 |
|
||||
|---|------|------|
|
||||
| 1 | `ExperionOpcClient.cs:108` | `Session.Create()` → `ISessionFactory.CreateAsync` 사용 권장 |
|
||||
| 2 | `ExperionRealtimeService.cs:161` | `Subscription.ApplyChanges()` → `ApplyChangesAsync()` 사용 권장 |
|
||||
| 3 | `ExperionRealtimeService.cs:168` | 동일 |
|
||||
| 4 | `ExperionRealtimeService.cs:277` | `Subscription.Create()` → `CreateAsync()` 사용 권장 |
|
||||
| 5 | `ExperionRealtimeService.cs:346` | `Subscription.Delete()` → `DeleteAsync()` 사용 권장 |
|
||||
| 6 | `ExperionRealtimeService.cs:424` | `Session.Create()` → `ISessionFactory.CreateAsync` 사용 권장 |
|
||||
| 7–8 | (위 항목 중 중복 카운트) | — |
|
||||
|
||||
전부 OPC UA SDK가 동기 메서드를 `[Obsolete]`로 표시하고 비동기 버전을 권장하는 경고. 기능상 문제 없음.
|
||||
|
||||
---
|
||||
|
||||
### 노드맵 대시보드 구현 (2026-04-14)
|
||||
|
||||
node_map_master 테이블을 조회·탐색할 수 있는 웹 대시보드를 풀스택으로 구현했다.
|
||||
@@ -315,6 +442,27 @@ at ExperionRealtimeService.<<OnNotification>b__0>d.MoveNext()
|
||||
|
||||
---
|
||||
|
||||
### TimescaleDB 관련 결정 사항 (2026-04-14)
|
||||
|
||||
PostgreSQL에 TimescaleDB 확장이 설치되어 있음.
|
||||
|
||||
#### 결론: 앱 코드 수정 불필요
|
||||
|
||||
TimescaleDB는 PostgreSQL **확장(extension)** 이므로:
|
||||
- 연결 문자열: 기존 PostgreSQL 그대로 사용
|
||||
- EF Core / Npgsql 드라이버: 그대로 사용
|
||||
- `history_table` hypertable 전환은 DB에서 DDL 한 줄만 실행
|
||||
|
||||
```sql
|
||||
SELECT create_hypertable('history_table', 'recorded_at');
|
||||
```
|
||||
|
||||
이 명령을 DB에서 한 번 실행하면 이후 INSERT/SELECT는 코드 변경 없이 TimescaleDB가 자동으로 시계열 최적화를 적용함.
|
||||
|
||||
**DbContext, 엔티티, 컨트롤러 등 앱 코드는 전혀 수정 불필요.**
|
||||
|
||||
---
|
||||
|
||||
## 구현 계획 (참고용)
|
||||
|
||||
### Task 1 — RealtimeTable + 포인트빌더 대시보드
|
||||
|
||||
Reference in New Issue
Block a user