From 4da37f4aa2cd96f0b8f925fbbdc06aa2f25a4888 Mon Sep 17 00:00:00 2001 From: kittapath Date: Sat, 18 Oct 2025 22:18:41 +0700 Subject: [PATCH] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20cal=20=20api=20?= =?UTF-8?q?=E0=B9=80=E0=B8=A2=E0=B8=AD=E0=B8=B0=E0=B9=84=E0=B8=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/PeriodExamService.cs | 66 +++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/Services/PeriodExamService.cs b/Services/PeriodExamService.cs index e0794e2..ecdea48 100644 --- a/Services/PeriodExamService.cs +++ b/Services/PeriodExamService.cs @@ -27,6 +27,7 @@ namespace BMA.EHR.Recurit.Exam.Service.Services private readonly MinIOService _minioService; private readonly MailService _mailService; private readonly IConfiguration _configuration; + private readonly IHttpClientFactory _httpClientFactory; #endregion @@ -38,7 +39,8 @@ namespace BMA.EHR.Recurit.Exam.Service.Services IHttpContextAccessor httpContextAccessor, MinIOService minioService, MailService mailService, - IConfiguration configuration) + IConfiguration configuration, + IHttpClientFactory httpClientFactory) { _context = context; _contextMetadata = contextMetadata; @@ -47,6 +49,7 @@ namespace BMA.EHR.Recurit.Exam.Service.Services _minioService = minioService; _mailService = mailService; _configuration = configuration; + _httpClientFactory = httpClientFactory; } #endregion @@ -3003,13 +3006,25 @@ namespace BMA.EHR.Recurit.Exam.Service.Services { try { - // 🚀 Prepare HTTP client once - var httpClient1 = new HttpClient(); - httpClient1.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); - httpClient1.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); + // 🚀 Prepare HTTP client once via factory with timeout + var clientForPos = _httpClientFactory.CreateClient("default"); + clientForPos.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); + clientForPos.DefaultRequestHeaders.Remove("api_key"); + clientForPos.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"] ?? ""); var apiUrl1 = $"{_configuration["API"]}/org/pos/level"; - var response1 = await httpClient1.GetStringAsync(apiUrl1); - var posOptions = JsonConvert.DeserializeObject(response1); + var response1 = string.Empty; + try + { + using var ctsPos = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + response1 = await clientForPos.GetStringAsync(apiUrl1, ctsPos.Token); + } + catch (TaskCanceledException) + { + // timeout - fallback to empty posOptions + response1 = string.Empty; + } + + var posOptions = string.IsNullOrWhiteSpace(response1) ? null : JsonConvert.DeserializeObject(response1); var periodExam = await _context.PeriodExams.AsQueryable() .Where(x => x.CheckDisability == true) @@ -3069,26 +3084,41 @@ namespace BMA.EHR.Recurit.Exam.Service.Services .Where(x => !string.IsNullOrWhiteSpace(x.ExamId)) .ToDictionary(x => x.ExamId, x => x); - // 🚀 Prepare HTTP client once - var httpClient = new HttpClient(); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); - httpClient.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"]); + // 🚀 Batch HTTP requests using IHttpClientFactory with concurrency limit and cancellation + var clientForOrg = _httpClientFactory.CreateClient("default"); + clientForOrg.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "")); + clientForOrg.DefaultRequestHeaders.Remove("api_key"); + clientForOrg.DefaultRequestHeaders.Add("api_key", _configuration["API_KEY"] ?? ""); - // 🚀 Batch HTTP requests + var semaphore = new SemaphoreSlim(10); // limit concurrency var orgTasks = candidates.Select(async candidate => { if (string.IsNullOrWhiteSpace(candidate.CitizenId)) return new { CitizenId = candidate.CitizenId ?? "", org = (dynamic?)null }; - var apiUrl = $"{_configuration["API"]}/org/profile/citizenid/position/{candidate.CitizenId}"; + await semaphore.WaitAsync(); try { - var response = await httpClient.GetStringAsync(apiUrl); - return new { CitizenId = candidate.CitizenId, org = JsonConvert.DeserializeObject(response) }; + var apiUrl = $"{_configuration["API"]}/org/profile/citizenid/position/{candidate.CitizenId}"; + try + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + var response = await clientForOrg.GetStringAsync(apiUrl, cts.Token); + return new { CitizenId = candidate.CitizenId, org = JsonConvert.DeserializeObject(response) }; + } + catch (TaskCanceledException) + { + // timeout + return new { CitizenId = candidate.CitizenId ?? "", org = (dynamic?)null }; + } + catch (Exception) + { + return new { CitizenId = candidate.CitizenId ?? "", org = (dynamic?)null }; + } } - catch + finally { - return new { CitizenId = candidate.CitizenId ?? "", org = (dynamic?)null }; + semaphore.Release(); } }).ToList(); @@ -3279,8 +3309,6 @@ namespace BMA.EHR.Recurit.Exam.Service.Services // 🚀 Single SaveChanges at the end await _contextMetadata.SaveChangesAsync(); - - httpClient.Dispose(); } catch {