삽질하다 도저히 문제 파악이 안돼서 opcUaManager로 분리 테스트 중
This commit is contained in:
223
opcUaManager/Services/DatabaseService.cs
Normal file
223
opcUaManager/Services/DatabaseService.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using Npgsql;
|
||||
using OpcUaManager.Models;
|
||||
|
||||
namespace OpcUaManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL opc_history 테이블에 대한 읽기/쓰기를 담당합니다.
|
||||
/// 원본 SaveToDatabase 함수의 INSERT 패턴을 그대로 유지하며,
|
||||
/// OPC 태그 읽기(OpcSessionService)와 DB 저장을 조율합니다.
|
||||
/// </summary>
|
||||
public class DatabaseService
|
||||
{
|
||||
private readonly ILogger<DatabaseService> _logger;
|
||||
private readonly OpcSessionService _sessionSvc;
|
||||
|
||||
public DatabaseService(ILogger<DatabaseService> logger, OpcSessionService sessionSvc)
|
||||
{
|
||||
_logger = logger;
|
||||
_sessionSvc = sessionSvc;
|
||||
}
|
||||
|
||||
// ── 연결 문자열 빌더 ──────────────────────────────────────────────
|
||||
|
||||
private static string BuildConnString(DbWriteRequest req)
|
||||
=> $"Host={req.DbHost};Username={req.DbUser};Password={req.DbPassword};Database={req.DbName}";
|
||||
|
||||
// ── DB/테이블 초기화 (최초 한 번) ────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// opc_history 테이블이 없으면 자동 생성합니다.
|
||||
/// 원본 코드에는 없었지만, 프로젝트화 시 처음 실행 환경을 위해 추가합니다.
|
||||
/// </summary>
|
||||
public async Task<(bool Ok, string Msg)> EnsureTableAsync(DbWriteRequest req)
|
||||
{
|
||||
const string ddl = """
|
||||
CREATE TABLE IF NOT EXISTS opc_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tag_name TEXT NOT NULL,
|
||||
tag_value DOUBLE PRECISION NOT NULL,
|
||||
status_code TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
""";
|
||||
try
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(BuildConnString(req));
|
||||
await conn.OpenAsync();
|
||||
await using var cmd = new NpgsqlCommand(ddl, conn);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
return (true, "테이블 확인/생성 완료");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "테이블 초기화 실패");
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// ── OPC 읽기 + DB 저장 루프 ──────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// OPC 노드를 req.Count 회 읽고 각 결과를 DB에 저장합니다.
|
||||
/// 원본 for 루프 + SaveToDatabase 패턴을 그대로 유지합니다.
|
||||
/// </summary>
|
||||
public async Task<DbWriteResult> WriteLoopAsync(DbWriteRequest req)
|
||||
{
|
||||
if (!_sessionSvc.IsConnected)
|
||||
return new DbWriteResult { Success = false, Message = "OPC 세션이 연결되어 있지 않습니다." };
|
||||
|
||||
var (tableOk, tableMsg) = await EnsureTableAsync(req);
|
||||
if (!tableOk)
|
||||
return new DbWriteResult { Success = false, Message = $"테이블 오류: {tableMsg}" };
|
||||
|
||||
var records = new List<DbWriteRecord>();
|
||||
int saved = 0;
|
||||
|
||||
for (int i = 1; i <= req.Count; i++)
|
||||
{
|
||||
// ── 1. OPC 태그 읽기 ────────────────────────────────────
|
||||
double val = 0;
|
||||
string status = "Unknown";
|
||||
bool dbSaved = false;
|
||||
|
||||
try
|
||||
{
|
||||
var (rawVal, sc) = await _sessionSvc.ReadValueAsync(req.TagNodeId);
|
||||
val = Convert.ToDouble(rawVal);
|
||||
status = sc;
|
||||
_logger.LogInformation("[{I}/{N}] {Tag} = {Val} ({Status})",
|
||||
i, req.Count, req.TagName, val, status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
status = $"ReadError: {ex.Message}";
|
||||
_logger.LogWarning("[{I}/{N}] OPC 읽기 실패: {Msg}", i, req.Count, ex.Message);
|
||||
}
|
||||
|
||||
// ── 2. DB 저장 (원본 SaveToDatabase 로직) ───────────────
|
||||
try
|
||||
{
|
||||
await SaveToDatabaseAsync(req, val, status);
|
||||
dbSaved = true;
|
||||
saved++;
|
||||
_logger.LogInformation("[{I}/{N}] DB 저장 완료.", i, req.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "[{I}/{N}] DB 저장 실패", i, req.Count);
|
||||
}
|
||||
|
||||
records.Add(new DbWriteRecord
|
||||
{
|
||||
Seq = i,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
TagName = req.TagName,
|
||||
Value = val,
|
||||
StatusCode = status,
|
||||
DbSaved = dbSaved
|
||||
});
|
||||
|
||||
// ── 3. 인터벌 대기 (마지막 회차는 생략) ─────────────────
|
||||
if (i < req.Count && req.IntervalMs > 0)
|
||||
await Task.Delay(req.IntervalMs);
|
||||
}
|
||||
|
||||
return new DbWriteResult
|
||||
{
|
||||
Success = saved > 0,
|
||||
Message = $"{saved}/{req.Count}회 저장 완료",
|
||||
SavedCount = saved,
|
||||
Records = records
|
||||
};
|
||||
}
|
||||
|
||||
// ── 원본 SaveToDatabase (INSERT 패턴 동일) ────────────────────────
|
||||
|
||||
private async Task SaveToDatabaseAsync(DbWriteRequest req, double val, string status)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(BuildConnString(req));
|
||||
await conn.OpenAsync();
|
||||
|
||||
// 원본 동일 INSERT
|
||||
const string sql =
|
||||
"INSERT INTO opc_history (tag_name, tag_value, status_code) " +
|
||||
"VALUES (@tag, @val, @status)";
|
||||
|
||||
await using var cmd = new NpgsqlCommand(sql, conn);
|
||||
cmd.Parameters.AddWithValue("tag", req.TagName);
|
||||
cmd.Parameters.AddWithValue("val", val);
|
||||
cmd.Parameters.AddWithValue("status", status);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
// ── DB 조회 ───────────────────────────────────────────────────────
|
||||
|
||||
public async Task<DbQueryResult> QueryRecentAsync(DbWriteRequest req, int limit = 100)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(BuildConnString(req));
|
||||
await conn.OpenAsync();
|
||||
|
||||
string sql = $"""
|
||||
SELECT id, tag_name, tag_value, status_code, created_at
|
||||
FROM opc_history
|
||||
ORDER BY id DESC
|
||||
LIMIT {limit}
|
||||
""";
|
||||
|
||||
await using var cmd = new NpgsqlCommand(sql, conn);
|
||||
await using var reader = await cmd.ExecuteReaderAsync();
|
||||
|
||||
var rows = new List<OpcHistoryRow>();
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
rows.Add(new OpcHistoryRow
|
||||
{
|
||||
Id = reader.GetInt64(0),
|
||||
TagName = reader.GetString(1),
|
||||
TagValue = reader.GetDouble(2),
|
||||
StatusCode = reader.GetString(3),
|
||||
CreatedAt = reader.GetDateTime(4)
|
||||
});
|
||||
}
|
||||
|
||||
// 전체 카운트
|
||||
await reader.CloseAsync();
|
||||
await using var cntCmd = new NpgsqlCommand("SELECT COUNT(*) FROM opc_history", conn);
|
||||
long total = (long)(await cntCmd.ExecuteScalarAsync() ?? 0L);
|
||||
|
||||
return new DbQueryResult
|
||||
{
|
||||
Success = true,
|
||||
TotalCount = (int)total,
|
||||
Rows = rows
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "DB 조회 실패");
|
||||
return new DbQueryResult { Success = false, Message = ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
// ── 연결 테스트 ───────────────────────────────────────────────────
|
||||
|
||||
public async Task<(bool Ok, string Msg)> TestConnectionAsync(DbWriteRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(BuildConnString(req));
|
||||
await conn.OpenAsync();
|
||||
await using var cmd = new NpgsqlCommand("SELECT version()", conn);
|
||||
var ver = await cmd.ExecuteScalarAsync();
|
||||
return (true, $"연결 성공: {ver}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user