using Microsoft.AspNetCore.Mvc; using Npgsql; using OpcPks.Core.Data; using OpcPks.Core.Services; using OpcPks.Core.Models; using System; using System.Collections.Generic; using System.Linq; using System.IO; using System.Threading.Tasks; namespace OpcPks.Web.Controllers; [Route("Engineering")] public class EngineeringController : Controller { // [현장 설정] 하니웰 데이터 및 PKI 경로 고정 private readonly string _basePath = "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/Data"; // [수정] 의존성 주입을 위한 필드 추가 private readonly CertificateGenerator _certGenerator; private readonly OpcSessionManager _sessionManager; // [수정] 생성자를 통해 Program.cs에서 등록된 서비스를 주입받음 public EngineeringController(CertificateGenerator certGenerator, OpcSessionManager sessionManager) { _certGenerator = certGenerator; _sessionManager = sessionManager; } #region [기존 기능: 태그 탐사 및 관리] [HttpGet("TagExplorer")] public IActionResult TagExplorer() => View(); [HttpGet("Admin")] public IActionResult Admin() => View(); [HttpPost("SearchByFilter")] public async Task SearchByFilter([FromBody] SearchRequest request) { var results = new List(); if (request?.Suffixes == null || request.Suffixes.Count == 0) return Json(results); using var conn = new NpgsqlConnection(DbConfig.ConnectionString); await conn.OpenAsync(); var suffixConditions = string.Join(" OR ", request.Suffixes.Select((s, i) => $"node_id ILIKE @s{i}")); var sql = $@"SELECT name, node_id, data_type FROM raw_node_map WHERE name ILIKE @tagTerm AND ({suffixConditions}) ORDER BY name ASC LIMIT 1500"; using var cmd = new NpgsqlCommand(sql, conn); cmd.Parameters.AddWithValue("tagTerm", $"%{request.TagTerm}%"); for (int i = 0; i < request.Suffixes.Count; i++) cmd.Parameters.AddWithValue($"s{i}", $"%{request.Suffixes[i]}"); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { results.Add(new { name = reader.GetString(0), nodeId = reader.GetString(1), dataType = reader.IsDBNull(2) ? "Double" : reader.GetString(2) }); } return Json(results); } [HttpPost("RegisterTags")] public async Task RegisterTags([FromBody] List tags) { if (tags == null || tags.Count == 0) return Ok(); using var conn = new NpgsqlConnection(DbConfig.ConnectionString); await conn.OpenAsync(); using var trans = await conn.BeginTransactionAsync(); try { var masterSql = @"INSERT INTO tag_master (server_name, area_code, tag_name, parameter, full_node_id, data_type) VALUES (@server, @area, @tag, @param, @nodeId, @type) ON CONFLICT (full_node_id) DO UPDATE SET data_type = EXCLUDED.data_type;"; var liveSql = @"INSERT INTO tag_live_data (full_node_id, live_value, quality) VALUES (@nodeId, '0', 'Initial') ON CONFLICT (full_node_id) DO NOTHING;"; foreach (var tag in tags) { if (string.IsNullOrEmpty(tag.NodeId)) continue; string sContent = tag.NodeId.Contains("s=") ? tag.NodeId.Split("s=")[1] : tag.NodeId; string[] parts = sContent.Split(':'); string server = parts[0]; string area = parts.Length >= 3 ? parts[1] : "unassigned"; string remains = parts.Last(); int lastDot = remains.LastIndexOf('.'); string tagName = (lastDot != -1) ? remains.Substring(0, lastDot) : remains; string param = (lastDot != -1) ? remains.Substring(lastDot + 1) : "pv"; using (var cmd = new NpgsqlCommand(masterSql, conn, trans)) { cmd.Parameters.AddWithValue("server", server); cmd.Parameters.AddWithValue("area", area); cmd.Parameters.AddWithValue("tag", tagName); cmd.Parameters.AddWithValue("param", param); cmd.Parameters.AddWithValue("nodeId", tag.NodeId); cmd.Parameters.AddWithValue("type", tag.DataType ?? "Double"); await cmd.ExecuteNonQueryAsync(); } using (var cmd = new NpgsqlCommand(liveSql, conn, trans)) { cmd.Parameters.AddWithValue("nodeId", tag.NodeId); await cmd.ExecuteNonQueryAsync(); } } await trans.CommitAsync(); return Ok(); } catch (Exception ex) { await trans.RollbackAsync(); return BadRequest(ex.Message); } } [HttpPost("RunCrawler")] public async Task RunCrawler() { try { // [수정] new 생성 대신 주입된 _sessionManager 사용 // GetSessionAsync에 필요한 인자(IP) 전달 (기본값 또는 모델 활용) var session = await _sessionManager.GetSessionAsync("192.168.0.20", new CertRequestModel()); if (session == null || !session.Connected) return BadRequest(new { message = "하니웰 서버 연결 실패." }); var crawler = new HoneywellCrawler(session); string csvPath = Path.Combine(_basePath, "Honeywell_FullMap.csv"); await crawler.RunAsync("ns=1;s=$assetmodel", csvPath); return Ok(new { message = "탐사 및 CSV 생성 완료!" }); } catch (Exception ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("ImportCsv")] public async Task ImportCsv() { try { using var conn = new NpgsqlConnection(DbConfig.ConnectionString); await conn.OpenAsync(); string csvPath = Path.Combine(_basePath, "Honeywell_FullMap.csv"); var sql = $@"TRUNCATE raw_node_map; COPY raw_node_map(level, node_class, name, node_id) FROM '{csvPath}' DELIMITER ',' CSV HEADER;"; using var cmd = new NpgsqlCommand(sql, conn); await cmd.ExecuteNonQueryAsync(); return Ok(new { message = "DB 동기화 완료" }); } catch (Exception ex) { return BadRequest(ex.Message); } } #endregion #region [안전 강화 기능: 하니웰 전용 인증서 관리] [HttpGet("CertManager")] public IActionResult CertManager() { // [약속] 통일된 경로 확인 (CertificateGenerator 내부 경로와 일치시킴) string pfxPath = Path.Combine(_basePath, "own/private/OpcPksClient.pfx"); ViewBag.IsCertExists = System.IO.File.Exists(pfxPath); ViewBag.SuccessMsg = TempData["Success"]; ViewBag.ErrorMsg = TempData["Error"]; return View(new CertRequestModel()); } [HttpPost("GenerateCertificate")] public async Task GenerateCertificate(CertRequestModel model) { try { // [수정] 직접 new 생성하던 코드를 지우고 주입된 _certGenerator 사용 // 이제 이 호출은 내부적으로 하니웰 서버와 3회 밀당을 수행합니다. var success = await _certGenerator.CreateHoneywellCertificateAsync(model); if (success) { TempData["Success"] = "인증서 생성 및 하니웰 서버 최종 수용 확인 완료!"; return RedirectToAction("CertManager"); } TempData["Error"] = "인증서 전송 과정에서 오류가 발생했습니다."; return RedirectToAction("CertManager"); } catch (Exception ex) { Console.WriteLine($"[FATAL_CERT] {DateTime.Now}: {ex.Message}"); TempData["Error"] = $"시스템 오류: {ex.Message}"; return RedirectToAction("CertManager"); } } #endregion public class SearchRequest { public string TagTerm { get; set; } = string.Empty; public List Suffixes { get; set; } = new(); } public class TagRegistrationRequest { public string TagName { get; set; } = string.Empty; public string NodeId { get; set; } = string.Empty; public string DataType { get; set; } = "Double"; } }