using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace AxCopilot.Services; internal sealed class Cp4dTokenService { private static readonly HttpClient _http = new HttpClient { Timeout = TimeSpan.FromSeconds(15.0) }; private static readonly Dictionary _cache = new Dictionary(); private static readonly object _lock = new object(); public static async Task GetTokenAsync(string cp4dUrl, string username, string password, CancellationToken ct = default(CancellationToken)) { if (string.IsNullOrWhiteSpace(cp4dUrl) || string.IsNullOrWhiteSpace(username)) { return null; } string cacheKey = cp4dUrl + "|" + username; lock (_lock) { if (_cache.TryGetValue(cacheKey, out var cached) && cached.Expiry > DateTime.UtcNow.AddMinutes(1.0)) { return cached.Token; } } try { string tokenUrl = cp4dUrl.TrimEnd('/') + "/icp4d-api/v1/authorize"; var body = new { username, password }; using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, tokenUrl) { Content = JsonContent.Create(body) }; using HttpResponseMessage resp = await _http.SendAsync(req, ct); if (!resp.IsSuccessStatusCode) { string errBody = await resp.Content.ReadAsStringAsync(ct); LogService.Warn($"CP4D 토큰 발급 실패: {resp.StatusCode} - {errBody}"); return null; } using JsonDocument doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync(ct)); if (!doc.RootElement.TryGetProperty("token", out var tokenProp)) { LogService.Warn("CP4D 응답에 token 필드가 없습니다."); return null; } string token = tokenProp.GetString(); if (string.IsNullOrEmpty(token)) { return null; } DateTime expiry = DateTime.UtcNow.AddHours(11.0); if (doc.RootElement.TryGetProperty("accessTokenExpiry", out var expiryProp) && expiryProp.TryGetInt64(out var expiryMs)) { expiry = DateTimeOffset.FromUnixTimeMilliseconds(expiryMs).UtcDateTime; } lock (_lock) { _cache[cacheKey] = (token, expiry); } LogService.Info($"CP4D 토큰 발급 완료: {cp4dUrl} (만료: {expiry:yyyy-MM-dd HH:mm} UTC)"); return token; } catch (Exception ex) { LogService.Error("CP4D 토큰 발급 오류: " + ex.Message); return null; } } public static void InvalidateToken(string cp4dUrl, string username) { string key = cp4dUrl + "|" + username; lock (_lock) { _cache.Remove(key); } } public static void ClearAllTokens() { lock (_lock) { _cache.Clear(); } } }