싸인 이미지 SMB 직접 읽기 — 공유 매핑 구현(서버측 SMB 개방 대기)

사용자 결정(②SMB 직접 접근)에 따라 구현. 실측 확인:
- 파일서버 = DB 서버와 같은 호스트, 전송 데몬 포트 2002 는 열림
- <b>SMB 445·139 는 닫힘</b>, UNC·관리공유 접근 모두 실패 → 서버측 조치 필요
- 서명 경로는 서버측 절대경로 D:\MsystechHIS\{사이트}\... (설정 루트 E:\msys\ 와 다름)

그래서 공유가 열리는 즉시 동작하도록 매핑을 넣었다:
Images:SignatureShareMap 에 "서버접두=UNC접두" 를 두면 해석 순서가
①레거시 캐시 ②공유(UNC) ③직접 경로가 된다. 접두는 대소문자 무시로
<b>긴 것부터</b> 맞추고(짧은 접두가 정확한 대응을 묻지 않게), '=' 없는
설정 오타는 무시한다(반쪽 경로를 만들면 조용히 틀린다). 자격증명은
설정에 담지 않는다 — 공유 접근은 단말 로그인 계정으로 붙는다.

진단 --db-filecfg 확장: 접속 <b>대상</b>(Type·IP·Port·FileDirectory)과
경로 접두 분포를 찍고(자격증명은 길이만), 공유 매핑별 접근 가능 여부와
파일서버 SMB(445) 개방 여부를 판정해 무엇을 열어야 하는지 알려 준다.

- dotnet test 358/358 (매핑 4건 추가: 치환·긴 접두 우선·오타 무시·해석 순서)
- --edit-smoke 실패 0 · --db-patient ①~㊲ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-20 12:40:56 +09:00
co-authored by Claude Fable 5
parent a0333791b6
commit 345b2e1da1
10 changed files with 214 additions and 12 deletions
+51 -3
View File
@@ -9,6 +9,9 @@ public enum SignatureImageOrigin
/// <summary>레거시 EMR 이 이미 내려받아 둔 캐시 파일</summary>
LegacyCache,
/// <summary>파일서버의 공유 폴더(UNC)로 직접 읽었다</summary>
Share,
/// <summary>DB 가 준 경로 자체가 이 단말에서 바로 열린다</summary>
DirectPath,
}
@@ -51,8 +54,12 @@ public static class SignatureImagePath
/// <param name="remotePath">DB 가 준 경로(M_UidMst.UidImgPth 등)</param>
/// <param name="installPath">HIS 설치 경로(ServerInfo ini 의 Install\Path)</param>
/// <param name="exists">파일 존재 판정 — 테스트가 갈아 끼운다</param>
/// <param name="shareMap">
/// 서버 경로 → UNC 공유 대응(<c>"D:\MsystechHIS=\\서버\MsystechHIS"</c> 형태).
/// 접두가 <b>긴 것부터</b> 맞춰 본다 — 짧은 접두가 먼저 걸리면 더 정확한 대응이 묻힌다.
/// </param>
public static SignatureImageResult Resolve(string? remotePath, string? installPath,
Func<string, bool>? exists = null)
Func<string, bool>? exists = null, IReadOnlyList<string>? shareMap = null)
{
var remote = (remotePath ?? string.Empty).Trim();
if (remote.Length == 0)
@@ -72,13 +79,54 @@ public static class SignatureImagePath
return new SignatureImageResult(SignatureImageOrigin.LegacyCache, cached, fileName);
}
}
// 캐시에 없으면 원격 경로가 이 단말에서 바로 열리는지 본다 —
// 이 병원의 저장 형태는 드라이브 절대경로다(진단 --db-filecfg 실측)
// 캐시에 없으면 파일서버 공유(UNC)로 직접 읽는다 — 설정된 접두 대응을 쓴다.
// 이 경로가 이 작업의 목적이다: OCX 전송을 재현하는 대신 같은 파일을 공유로 본다.
if (MapToShare(remote, shareMap) is { Length: > 0 } shared && probe(shared))
{
return new SignatureImageResult(SignatureImageOrigin.Share, shared, fileName);
}
// 그래도 없으면 원격 경로가 이 단말에서 그대로 열리는지 본다
// (서버에서 실행하거나 같은 구조로 마운트된 단말)
if (probe(remote))
{
return new SignatureImageResult(SignatureImageOrigin.DirectPath, remote, fileName);
}
return new SignatureImageResult(SignatureImageOrigin.None, string.Empty, fileName);
}
/// <summary>
/// 서버 경로를 UNC 로 바꾼다 — 대응이 없으면 빈 문자열.
/// 대소문자를 무시하고, 접두가 <b>긴 것부터</b> 맞춘다.
/// </summary>
public static string MapToShare(string? remotePath, IReadOnlyList<string>? shareMap)
{
var remote = (remotePath ?? string.Empty).Trim();
if (remote.Length == 0 || shareMap is null || shareMap.Count == 0)
{
return string.Empty;
}
var best = string.Empty;
var bestLen = -1;
foreach (var entry in shareMap)
{
var at = entry.IndexOf('=', StringComparison.Ordinal);
if (at <= 0 || at == entry.Length - 1)
{
continue; // "서버접두=UNC접두" 가 아니면 무시한다(설정 오타를 값으로 바꾸지 않는다)
}
var from = entry[..at].Trim().TrimEnd('\\');
var to = entry[(at + 1)..].Trim().TrimEnd('\\');
if (from.Length == 0 || to.Length == 0 || from.Length <= bestLen)
{
continue;
}
if (remote.StartsWith(from + "\\", StringComparison.OrdinalIgnoreCase))
{
best = to + remote[from.Length..];
bestLen = from.Length;
}
}
return best;
}
#endregion
}