You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
500 lines
18 KiB
500 lines
18 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Housing.Services
|
|
{
|
|
// 1. DatabaseSettings
|
|
public class DatabaseSettings
|
|
{
|
|
public string Db_Server { get; set; } = string.Empty;
|
|
public string Db_Name { get; set; } = string.Empty;
|
|
public string Db_User { get; set; } = string.Empty;
|
|
public string Db_Password { get; set; } = string.Empty;
|
|
|
|
private string _rawConnectionString = string.Empty;
|
|
|
|
public string ConnectionString
|
|
{
|
|
get
|
|
{
|
|
string connStr;
|
|
if (!string.IsNullOrWhiteSpace(Db_Server) &&
|
|
!string.IsNullOrWhiteSpace(Db_Name) &&
|
|
!string.IsNullOrWhiteSpace(Db_User) &&
|
|
!string.IsNullOrWhiteSpace(Db_Password))
|
|
{
|
|
string cleanServer = Db_Server.Trim().Replace(" ", "");
|
|
connStr = $"Server={cleanServer};Database={Db_Name};User Id={Db_User};Password={Db_Password};";
|
|
}
|
|
else
|
|
{
|
|
connStr = _rawConnectionString;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(connStr) && connStr.IndexOf("TrustServerCertificate", StringComparison.OrdinalIgnoreCase) < 0)
|
|
{
|
|
if (!connStr.EndsWith(";")) connStr += ";";
|
|
connStr += "TrustServerCertificate=True;";
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(connStr) &&
|
|
connStr.IndexOf("Connect Timeout", StringComparison.OrdinalIgnoreCase) < 0 &&
|
|
connStr.IndexOf("Connection Timeout", StringComparison.OrdinalIgnoreCase) < 0)
|
|
{
|
|
if (!connStr.EndsWith(";")) connStr += ";";
|
|
connStr += "Connect Timeout=3;";
|
|
}
|
|
|
|
return connStr;
|
|
}
|
|
set
|
|
{
|
|
_rawConnectionString = value;
|
|
}
|
|
}
|
|
|
|
public static DatabaseSettings Load(string path)
|
|
{
|
|
var settings = new DatabaseSettings();
|
|
try
|
|
{
|
|
if (File.Exists(path))
|
|
{
|
|
var lines = File.ReadAllLines(path);
|
|
foreach (var line in lines)
|
|
{
|
|
var trimmed = line.Trim();
|
|
|
|
if (trimmed.StartsWith(";") || trimmed.StartsWith("#"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
settings.ConnectionString = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Db_Server", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
settings.Db_Server = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Db_Name", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
settings.Db_Name = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Db_User", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
settings.Db_User = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Db_Password", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
settings.Db_Password = parts[1].Trim();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// 로드 실패 시 무시
|
|
}
|
|
return settings;
|
|
}
|
|
}
|
|
|
|
// 2. LoginSettings
|
|
public class LoginSettings
|
|
{
|
|
public bool OfflinePreview { get; set; } = true; // 기본값은 오프라인 테스트 편의를 위해 true
|
|
public string Procedure { get; set; } = string.Empty;
|
|
public string ProcessName { get; set; } = string.Empty;
|
|
|
|
public static LoginSettings Load(string path)
|
|
{
|
|
var settings = new LoginSettings();
|
|
try
|
|
{
|
|
if (File.Exists(path))
|
|
{
|
|
var lines = File.ReadAllLines(path);
|
|
foreach (var line in lines)
|
|
{
|
|
var trimmed = line.Trim();
|
|
if (trimmed.StartsWith("OfflinePreview", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
if (bool.TryParse(parts[1].Trim(), out var val))
|
|
{
|
|
settings.OfflinePreview = val;
|
|
}
|
|
else if (int.TryParse(parts[1].Trim(), out var intVal))
|
|
{
|
|
settings.OfflinePreview = intVal != 0;
|
|
}
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Procedure", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
settings.Procedure = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("ProcessName", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
settings.ProcessName = parts[1].Trim();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// 로드 실패 시 무시
|
|
}
|
|
return settings;
|
|
}
|
|
}
|
|
|
|
// 3. LoginHistoryStore
|
|
public class LoginHistoryStore
|
|
{
|
|
private readonly string _filePath;
|
|
private readonly Dictionary<string, List<string>> _history = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
private LoginHistoryStore(string filePath)
|
|
{
|
|
_filePath = filePath;
|
|
}
|
|
|
|
public static LoginHistoryStore Load(string path)
|
|
{
|
|
var store = new LoginHistoryStore(path);
|
|
store.LoadFromFile();
|
|
return store;
|
|
}
|
|
|
|
public List<string> GetValues(string fieldName)
|
|
{
|
|
if (_history.TryGetValue(fieldName, out var list))
|
|
{
|
|
return new List<string>(list);
|
|
}
|
|
return new List<string>();
|
|
}
|
|
|
|
public void Remember(string fieldName, string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value)) return;
|
|
value = value.Trim();
|
|
|
|
if (!_history.TryGetValue(fieldName, out var list))
|
|
{
|
|
list = new List<string>();
|
|
_history[fieldName] = list;
|
|
}
|
|
|
|
// 중복 제거 및 맨 앞으로 이동
|
|
list.RemoveAll(x => string.Equals(x, value, StringComparison.OrdinalIgnoreCase));
|
|
list.Insert(0, value);
|
|
|
|
// 최대 5개 이력 유지
|
|
if (list.Count > 5)
|
|
{
|
|
list.RemoveRange(5, list.Count - 5);
|
|
}
|
|
}
|
|
|
|
public bool Forget(string fieldName, string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value)) return false;
|
|
value = value.Trim();
|
|
|
|
if (_history.TryGetValue(fieldName, out var list))
|
|
{
|
|
bool removed = list.RemoveAll(x => string.Equals(x, value, StringComparison.OrdinalIgnoreCase)) > 0;
|
|
return removed;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
try
|
|
{
|
|
var dir = Path.GetDirectoryName(_filePath);
|
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
|
|
// 초간단 JSON 형식으로 직렬화 (의존성 최소화)
|
|
using (var sw = new StreamWriter(_filePath))
|
|
{
|
|
sw.WriteLine("{");
|
|
var keys = new List<string>(_history.Keys);
|
|
for (int i = 0; i < keys.Count; i++)
|
|
{
|
|
var key = keys[i];
|
|
var values = _history[key];
|
|
var arrayItems = new List<string>();
|
|
foreach (var val in values)
|
|
{
|
|
// 따옴표 escape
|
|
var escaped = val.Replace("\"", "\\\"");
|
|
arrayItems.Add($"\"{escaped}\"");
|
|
}
|
|
var line = $" \"{key}\": [{string.Join(", ", arrayItems)}]";
|
|
if (i < keys.Count - 1)
|
|
{
|
|
line += ",";
|
|
}
|
|
sw.WriteLine(line);
|
|
}
|
|
sw.WriteLine("}");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// 저장 오류 시 무시
|
|
}
|
|
}
|
|
|
|
private void LoadFromFile()
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(_filePath)) return;
|
|
|
|
var text = File.ReadAllText(_filePath);
|
|
// 정규식이나 아주 심플한 파싱으로 키와 배열 파싱
|
|
// 형식 예: "Maker": ["MOBI", "TEST"]
|
|
var matches = Regex.Matches(text, @"""([^""]+)""\s*:\s*\[([^\]]*)\]");
|
|
foreach (Match match in matches)
|
|
{
|
|
var key = match.Groups[1].Value;
|
|
var arrayContent = match.Groups[2].Value;
|
|
var valueMatches = Regex.Matches(arrayContent, @"""([^""]*)""");
|
|
|
|
var list = new List<string>();
|
|
foreach (Match valMatch in valueMatches)
|
|
{
|
|
list.Add(valMatch.Groups[1].Value);
|
|
}
|
|
_history[key] = list;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// 로드 오류 시 무시
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. LoginAccountRepository
|
|
public class LoginAccountRepository
|
|
{
|
|
private readonly DatabaseSettings _dbSettings;
|
|
private readonly LoginSettings _loginSettings;
|
|
|
|
public LoginAccountRepository(DatabaseSettings dbSettings, LoginSettings loginSettings)
|
|
{
|
|
_dbSettings = dbSettings;
|
|
_loginSettings = loginSettings;
|
|
}
|
|
|
|
public async Task<bool> ExistsAsync(string operatorId, string password)
|
|
{
|
|
// 오프라인 모드인 경우 기본 성공
|
|
if (_loginSettings.OfflinePreview)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// DB 커넥션 스트링이 비어있으면 로그인 불가
|
|
if (string.IsNullOrWhiteSpace(_dbSettings.ConnectionString))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// 실제 DB에서 계정을 조회해보기 위한 시도
|
|
using (var conn = new System.Data.SqlClient.SqlConnection(_dbSettings.ConnectionString))
|
|
{
|
|
await conn.OpenAsync();
|
|
|
|
if (!string.IsNullOrWhiteSpace(_loginSettings.Procedure))
|
|
{
|
|
using (var cmd = new System.Data.SqlClient.SqlCommand(_loginSettings.Procedure, conn))
|
|
{
|
|
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
|
|
cmd.CommandType = System.Data.CommandType.StoredProcedure;
|
|
cmd.Parameters.AddWithValue("@Operator", operatorId);
|
|
cmd.Parameters.AddWithValue("@Password", password);
|
|
if (!string.IsNullOrWhiteSpace(_loginSettings.ProcessName))
|
|
{
|
|
cmd.Parameters.AddWithValue("@ProcessName", _loginSettings.ProcessName);
|
|
}
|
|
|
|
var resultObj = await cmd.ExecuteScalarAsync();
|
|
if (resultObj != null)
|
|
{
|
|
if (int.TryParse(resultObj.ToString(), out var count))
|
|
{
|
|
return count > 0;
|
|
}
|
|
if (bool.TryParse(resultObj.ToString(), out var boolVal))
|
|
{
|
|
return boolVal;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 우선 Users 또는 Accounts와 같이 로그인 테이블명이 뭔지 모를 수 있으므로
|
|
// 쿼리 실패 시 default 계정 검증으로 fallback 하도록 구성
|
|
const string query = "SELECT COUNT(*) FROM [Users] WHERE [OperatorId] = @Id AND [Password] = @Pw";
|
|
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
|
|
{
|
|
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
|
|
cmd.Parameters.AddWithValue("@Id", operatorId);
|
|
cmd.Parameters.AddWithValue("@Pw", password);
|
|
|
|
var count = (int)await cmd.ExecuteScalarAsync();
|
|
return count > 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// DB 연결이나 쿼리 오류 발생 시 상위로 예외 전달 (DB 연결 상태 확인 안내 팝업 표출을 위함)
|
|
System.Diagnostics.Debug.WriteLine($"로그인 DB 검증 중 에러 발생: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private bool IsDefaultAccount(string operatorId, string password)
|
|
{
|
|
// 하드코딩 테스트 계정 완전 제거 (LoginOptions.json 및 DB 계정만 사용)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 5. LoginAccount (For LoginOptions.json)
|
|
public class LoginAccount
|
|
{
|
|
public string Id { get; set; } = string.Empty;
|
|
public string Password { get; set; } = string.Empty;
|
|
}
|
|
|
|
// 6. LoginSelectionOptions (For LoginOptions.json)
|
|
public class LoginSelectionOptions
|
|
{
|
|
public List<LoginAccount> LoginAccounts { get; set; } = new List<LoginAccount>();
|
|
public List<string> Makers { get; set; } = new List<string>();
|
|
public List<string> Models { get; set; } = new List<string>();
|
|
public List<string> Variant1Values { get; set; } = new List<string>();
|
|
public List<string> Variant2Values { get; set; } = new List<string>();
|
|
public List<string> LineValues { get; set; } = new List<string>();
|
|
public List<string> JigNoValues { get; set; } = new List<string>();
|
|
|
|
public static LoginSelectionOptions Load(string path)
|
|
{
|
|
try
|
|
{
|
|
if (File.Exists(path))
|
|
{
|
|
var json = File.ReadAllText(path);
|
|
var options = new System.Text.Json.JsonSerializerOptions
|
|
{
|
|
PropertyNameCaseInsensitive = true
|
|
};
|
|
var loaded = System.Text.Json.JsonSerializer.Deserialize<LoginSelectionOptions>(json, options);
|
|
if (loaded != null)
|
|
{
|
|
return loaded;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("Failed to load LoginOptions: " + ex.Message);
|
|
}
|
|
return new LoginSelectionOptions();
|
|
}
|
|
|
|
public static void CreateDefaultFile(string path)
|
|
{
|
|
try
|
|
{
|
|
var dir = Path.GetDirectoryName(path);
|
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
|
|
var defaultContent = @"{
|
|
""loginAccounts"": [
|
|
{
|
|
""id"": ""su"",
|
|
""password"": ""su""
|
|
}
|
|
],
|
|
""makers"": [
|
|
""AMO"",
|
|
""ABC""
|
|
],
|
|
""models"": [
|
|
""MODEL-1""
|
|
],
|
|
""variant1Values"": [],
|
|
""variant2Values"": [],
|
|
""lineValues"": [
|
|
""LINE-1""
|
|
],
|
|
""jigNoValues"": [
|
|
""JIG-1""
|
|
]
|
|
}";
|
|
File.WriteAllText(path, defaultContent);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("Failed to create default LoginOptions file: " + ex.Message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|