using System.Data; using System.Text; using YamlDotNet.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Linq; using System.Threading.Tasks; namespace KIS_Common { class Common { private static string configRoot = @"d:\KIS\config\"; private static string tokenTmp = configRoot + "KIS" + DateTime.Now.ToString("yyyyMMdd"); private static Dictionary _cfg; public static (string my_app, string my_sec , string my_acct, string my_prod, string my_token, string my_url) _TRENV; private static DateTime _lastAuthTime; private static bool _DEBUG = false; public static bool _isPaper = false; private static Dictionary _baseHeaders; private static string _mode = "prod"; public static int _rescode; public static HttpResponseMessage _resp; public static Dictionary _header; public static Dictionary _body; public static string _errCode; public static string _errMessage; public static async Task doAuth(String mode) { if (!File.Exists(tokenTmp)){ File.Create(tokenTmp).Close(); } using (var reader = new StreamReader(configRoot + "kis_devlp.yaml", Encoding.UTF8)){ var deserializer = new Deserializer(); _cfg = deserializer.Deserialize>(reader); } _baseHeaders = new Dictionary{ { "Content-Type", "application/json" }, { "Accept", "text/plain" }, { "charset", "UTF-8" }, { "User-Agent", _cfg["my_agent"] } }; if(mode.Equals("V")) { _DEBUG = true; _mode = "vps"; await Auth(_mode, _cfg["my_prod"]); } else if(mode.Equals("D")) { _DEBUG = true; _mode = "dev"; await Auth(_mode, _cfg["my_prod"]); } else { _DEBUG = false; _mode = "prod"; await Auth(_mode, _cfg["my_prod"]); } } private static void SaveToken(string myToken, string myExpired, string mode) { Console.WriteLine("===== Exp " + myExpired); using (var writer = new StreamWriter(tokenTmp, false, Encoding.UTF8)) { writer.WriteLine($"token; {myToken}"); writer.WriteLine($"valid-date; {myExpired}"); writer.Write($"mode; {mode}"); } } private static string? ReadToken() { try { var lines = File.ReadAllLines(tokenTmp, Encoding.UTF8); var tokenData = lines.Select(line => line.Split(';')).ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim()); string expDt = DateTime.Parse(tokenData["valid-date"]).ToString("yyyy-MM-dd HH:mm:ss"); string nowDt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string getMode = tokenData["mode"]; Console.WriteLine("Mode : " + getMode); if (string.Compare(expDt, nowDt) > 0 && _mode.Equals(getMode)){ return tokenData["token"]; } else{ return null; } } catch { return null; } } private static Dictionary GetBaseHeader() { DateTime n2 = DateTime.Now; if ((n2 - _lastAuthTime).TotalSeconds >= 86400){ ReAuth(_mode, _cfg["my_prod"]); } return new Dictionary(_baseHeaders); } private static (string my_app, string my_sec , string my_acct, string my_prod, string my_token, string my_url) GetTRENV() { return _TRENV; } public static void SetTRENV(Dictionary cfg) { _TRENV = (cfg["my_app"],cfg["my_sec"],cfg["my_acct"],cfg["my_prod"],cfg["my_token"],cfg["my_url"]); } private static void ChangeTREnv(string tokenKey, string svr, string product) { var cfg = new Dictionary(); if (svr == "prod") { cfg["my_app"] = _cfg["my_app"]; cfg["my_sec"] = _cfg["my_sec"]; cfg["my_acct"] = _cfg["my_acct"]; _isPaper = false; } else if (svr == "vps") { cfg["my_app"] = _cfg["paper_app"]; cfg["my_sec"] = _cfg["paper_sec"]; cfg["my_acct"] = _cfg["paper_acct"]; _isPaper = true; } else { cfg["my_app"] = _cfg["dev_app"]; cfg["my_sec"] = _cfg["dev_sec"]; cfg["my_acct"] = _cfg["dev_acct"]; _isPaper = false; } cfg["my_prod"] = product; cfg["my_token"] = tokenKey; cfg["my_url"] = _cfg[svr]; SetTRENV(cfg); } private static async TaskAuth(string svr, string product) { var p = new Dictionary{ { "grant_type", "client_credentials" } }; string ak1 = svr == "prod" ? "my_app" : (svr == "vps" ? "paper_app" : "dev_app"); string ak2 = svr == "prod" ? "my_sec" : (svr == "vps" ? "paper_sec" : "dev_sec"); p["appkey"] = _cfg[ak1]; p["appsecret"] = _cfg[ak2]; string savedToken = ReadToken(); if (savedToken == null){ var url = $"{_cfg[svr]}/oauth2/tokenP"; var client = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Post, url); request.Content = new StringContent(JsonConvert.SerializeObject(p), Encoding.UTF8, "application/json"); var response = client.SendAsync(request).Result; if (response.IsSuccessStatusCode){ var jsonData = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); string myToken = jsonData["access_token"]; string myExpired = jsonData["access_token_token_expired"]; SaveToken(myToken, myExpired, svr); savedToken = ReadToken(); ChangeTREnv($"Bearer {savedToken}", svr, product); } else{ Console.WriteLine("Get Authentication token fail!\nYou have to restart your app!!!"); return null; } } else{ ChangeTREnv($"Bearer {savedToken}", svr, product); } _baseHeaders["authorization"] = _TRENV.Item5; _baseHeaders["appkey"] = _TRENV.Item1; _baseHeaders["appsecret"] = _TRENV.Item2; _lastAuthTime = DateTime.Now; if (_DEBUG) { Console.WriteLine($"[{_lastAuthTime}] => get AUTH Key completed!"); } return null; } private static async void ReAuth(string svr, string product) { await Auth(svr, product); } private static Dictionary GetEnv() { return _cfg; } public static (string my_app, string my_sec , string my_acct, string my_prod, string my_token, string my_url) GetTREnv() { return _TRENV; } private static async Task SetOrderHashKey(Dictionary h, Dictionary p) { string url = $"{GetTREnv().my_url}/uapi/hashkey"; using (var client = new HttpClient()) { var content = new StringContent(JsonConvert.SerializeObject(p), Encoding.UTF8, "application/json"); var response = await client.PostAsync(url, content); if (response.IsSuccessStatusCode) { var jsonData = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); h["hashkey"] = jsonData["HASH"]; } else { Console.WriteLine("Error: " + response.StatusCode); } } } private static bool IsPaperTrading() { return _isPaper; } // 응답값 사용을 위한 변수 세팅 메서드 public static void SetAPIRespVal(HttpResponseMessage resp) { _rescode = (int)resp.StatusCode; _resp = resp; _header = _SetHeader(); _body = _SetBody(); _errCode = (string)_body["msg_cd"]; _errMessage = (string)_body["msg1"]; } // 응답코드 리턴값 public int GetResCode() { return _rescode; } // 응답메시지 리턴값 public HttpResponseMessage GetResponse() { return _resp; } // 트랜잭션 에러코드 리턴값 public string GetErrorCode() { return _errCode; } // 트랜잭션 에러메시지 리턴값 public string GetErrorMessage() { return _errMessage; } // 헤더 지정 private static Dictionary _SetHeader() { return _resp.Headers.ToDictionary(x => x.Key.ToLower(), x => string.Join(", ", x.Value)); } // 헤더 수신 public Dictionary GetHeader() { return _header; } // 바디 지정 private static Dictionary _SetBody() { return JsonConvert.DeserializeObject>(_resp.Content.ReadAsStringAsync().Result); } // 바디 수신 public Dictionary GetBody() { return _body; } // 트랜잭션 정상여부 확인 부울 값 public bool IsOK() { return (string) _body["rt_cd"] == "0"; } // 디버그 출력용 public static void PrintAll() { Common ka = new Common(); Console.WriteLine("
"); foreach (var x in ka.GetHeader().Keys) { Console.WriteLine($"\t-{x}: {ka.GetHeader()[x]}"); } Console.WriteLine(""); foreach (var x in ka.GetBody().Keys) { Console.WriteLine($"\t-{x}: {ka.GetBody()[x]}"); } } // 에러 메시지 출력용 public void PrintError(string url) { Console.WriteLine("-------------------------------\nError in response: " + GetResCode() + " url=" + url); Console.WriteLine("rt_cd : " + GetBody()["rt_cd"] + " / msg_cd : " + GetErrorCode() + " / msg1 : " + GetErrorMessage()); Console.WriteLine("-------------------------------"); } // 특정 JToken 결과값을 DataTable로 변환하는 메서드 public static DataTable ConvertToDataTable(JToken? data){ DataTable dataTable = new DataTable(); JArray jArray = new JArray(); if (data is null) { Console.WriteLine("조회 값 없음"); return null; } else { Console.WriteLine("Return Type : " + data.Type.ToString()); } if (data.Type.ToString().Equals("Array")) { jArray = (JArray) data; } else { jArray.Add(data); } if (jArray.Count > 0) { // Add columns to DataTable foreach (var firstRow in jArray.Children()) { foreach (var property in firstRow.Properties()) { if (!dataTable.Columns.Contains(property.Name)) { dataTable.Columns.Add(property.Name); } } break; } // Add rows to DataTable foreach (var row in jArray.Children()) { DataRow dataRow = dataTable.NewRow(); foreach (var property in row.Properties()) { dataRow[property.Name] = property.Value.ToString(); } dataTable.Rows.Add(dataRow); } } return dataTable; } // JObject 결과값을 DataTable로 변환하는 메서드 public static DataTable ConvertToDataTable(JObject data) { var dataTable = new DataTable(); Console.WriteLine("Return Type : " + data.Type.ToString()); foreach (var property in data.Properties()) { dataTable.Columns.Add(property.Name); } var row = dataTable.NewRow(); foreach (var property in data.Properties()) { row[property.Name] = property.Value; } dataTable.Rows.Add(row); return dataTable; } // DataTable 출력용 public static void PrintDataTable(DataTable dataTable) { string line = ""; foreach (DataColumn item in dataTable.Columns) { line += item.ColumnName +" "; } line += "\n"; foreach (DataRow row in dataTable.Rows) { for (int i = 0; i < dataTable.Columns.Count; i++) { line += row[i].ToString() + " "; } line += "\n"; } Console.WriteLine("=============================================================================="); Console.WriteLine(line) ; } // API 호출 및 응답값 회신 public static Task UrlFetch(Dictionary paramsDict, string apiUrl = "", string ptrId = "", string trCont = "", Dictionary? appendHeaders = null, bool postFlag = true , bool hashFlag = false) { // 요청 URL string url = $"{GetTREnv().my_url}{apiUrl}"; // HTTPClient 사용, RestSharp 사용 시 별도 코드 변환 필요 var client = new HttpClient(); HttpResponseMessage resp = new HttpResponseMessage(); // 최대 타임아웃 설정 → 기본 100ms, 네트워크 상황에 따라 TimeSpan으로 타임아웃 시간 설정 client.Timeout = TimeSpan.FromMinutes(10); // 요청 헤더 설정 var headers = GetBaseHeader(); string trId = ptrId; if (IsPaperTrading()){ //모의투자인 경우, 트랜잭션 ID 앞에 'V'가 붙음 if (ptrId[0] == 'T' || ptrId[0] == 'J' || ptrId[0] == 'C'){ trId = "V" + ptrId.Substring(1); } } headers["tr_id"] = trId; headers["custtype"] = "P"; headers["tr_cont"] = trCont; if (appendHeaders != null && appendHeaders.Count > 0) { foreach (var x in appendHeaders.Keys) { headers[x] = appendHeaders[x]; } } // 클라이언트 요청 헤더 값에 최종 설정 foreach (var x in headers) { client.DefaultRequestHeaders.TryAddWithoutValidation(x.Key , x.Value); } // 디버그 기능 사용 시, 출력용 if (_DEBUG) { Console.WriteLine("< Sending Info >"); Console.WriteLine($"URL: {url}, TR: {trId}"); Console.WriteLine($"
\n{string.Join(", ", headers.Select(h => $"{h.Key}: {h.Value}"))}"); Console.WriteLine($"\n{string.Join(", ", paramsDict.Select(p => $"{p.Key}: {p.Value}"))}"); } // POST 방식, GET 방식에 따른 분기 if (postFlag){ resp = client.PostAsync(url, new StringContent(JsonConvert.SerializeObject(paramsDict), Encoding.UTF8, "application/json")).ConfigureAwait(false).GetAwaiter().GetResult(); } else{ resp = client.GetAsync(url + "?" + string.Join("&", paramsDict.Select(p => $"{p.Key}={p.Value}"))).ConfigureAwait(false).GetAwaiter().GetResult(); } // 응답코드가 성공으로 나온 경우 결과 값을 최종 설정함. 오류인 경우에는 오류코드와 메시지 출력 if (resp.IsSuccessStatusCode){ SetAPIRespVal(resp); if (_DEBUG) { PrintAll(); } } else{ Console.WriteLine("Error Code : " + (int)resp.StatusCode + " | " + resp.Content); } return Task.FromResult(resp); } } }