From 3ae9be586902fe4c536dab53f28cc030861b248e Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 28 Aug 2025 12:33:18 +0700 Subject: [PATCH] add Background Task --- BMA.EHR.Insignia/BMA.EHR.Insignia.csproj | 1 + .../Controllers/InsigniaRequestController.cs | 20 ++- BMA.EHR.Insignia/Program.cs | 5 +- .../Services/BackgroundTaskQueue.cs | 26 +++ .../Services/IBackgroundTaskQueue.cs | 8 + .../Services/InsigniaRequestProcessService.cs | 153 ++++++++++++++++++ 6 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 BMA.EHR.Insignia/Services/BackgroundTaskQueue.cs create mode 100644 BMA.EHR.Insignia/Services/IBackgroundTaskQueue.cs create mode 100644 BMA.EHR.Insignia/Services/InsigniaRequestProcessService.cs diff --git a/BMA.EHR.Insignia/BMA.EHR.Insignia.csproj b/BMA.EHR.Insignia/BMA.EHR.Insignia.csproj index 93282da5..5903ceb7 100644 --- a/BMA.EHR.Insignia/BMA.EHR.Insignia.csproj +++ b/BMA.EHR.Insignia/BMA.EHR.Insignia.csproj @@ -41,6 +41,7 @@ + diff --git a/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs b/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs index 29cab3d2..dbb553ee 100644 --- a/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs +++ b/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs @@ -13,6 +13,8 @@ using BMA.EHR.Domain.ModelsExam.Candidate; using BMA.EHR.Domain.Shared; using BMA.EHR.Infrastructure.Persistence; using BMA.EHR.Insignia.Service.Requests; +using BMA.EHR.Insignia.Service.Services; +using Hangfire.Processing; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -51,6 +53,8 @@ namespace BMA.EHR.Insignia.Service.Controllers private readonly IConfiguration _configuration; private readonly PermissionRepository _permission; + private readonly IBackgroundTaskQueue _queue; + /// /// /// @@ -73,7 +77,8 @@ namespace BMA.EHR.Insignia.Service.Controllers InsigniaPeriodsRepository insigniaPeriodRepository, InsigniaReportRepository insigniaReportRepository, IConfiguration configuration, - PermissionRepository permission) + PermissionRepository permission, + IBackgroundTaskQueue queue) { _context = context; _documentService = documentService; @@ -86,6 +91,8 @@ namespace BMA.EHR.Insignia.Service.Controllers _configuration = configuration; _permission = permission; _insigniaReportRepository = insigniaReportRepository; + + _queue = queue; } #region " Properties " @@ -626,6 +633,17 @@ namespace BMA.EHR.Insignia.Service.Controllers return Success(); } + [HttpGet("bg/{type}/{insigniaPeriodId:length(36)}")] + public async Task> BackgroundCalculateInsigniaRequestByTypeAsync(string type, Guid insigniaPeriodId) + { + await _queue.QueueBackgroundWorkItemAsync(async token => + { + // Logic งาน background จริงเช่น: + //await LongRunningProcess(jobInfo, token); + }); + return Success("Background job started."); + } + /// /// คำนวณราชชื่อผู้ได้รับเครื่องราช (แยกตาม officer, employee) /// diff --git a/BMA.EHR.Insignia/Program.cs b/BMA.EHR.Insignia/Program.cs index 2b8f4537..cd3684fe 100644 --- a/BMA.EHR.Insignia/Program.cs +++ b/BMA.EHR.Insignia/Program.cs @@ -5,6 +5,7 @@ using BMA.EHR.Infrastructure; using BMA.EHR.Infrastructure.Persistence; using BMA.EHR.Insignia.Service; using BMA.EHR.Insignia.Service.Filters; +using BMA.EHR.Insignia.Service.Services; using Hangfire; using Hangfire.Common; using Hangfire.MySql; @@ -26,7 +27,6 @@ var builder = WebApplication.CreateBuilder(args); var issuer = builder.Configuration["Jwt:Issuer"]; var key = builder.Configuration["Jwt:Key"]; - IdentityModelEventSource.ShowPII = true; builder.Services.AddHttpContextAccessor(); @@ -88,6 +88,9 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddLeaveApplication(); builder.Services.AddLeavePersistence(builder.Configuration); + builder.Services.AddSingleton(); + builder.Services.AddHostedService(); + builder.Services.AddControllers(options => diff --git a/BMA.EHR.Insignia/Services/BackgroundTaskQueue.cs b/BMA.EHR.Insignia/Services/BackgroundTaskQueue.cs new file mode 100644 index 00000000..31805486 --- /dev/null +++ b/BMA.EHR.Insignia/Services/BackgroundTaskQueue.cs @@ -0,0 +1,26 @@ +using System.Collections.Concurrent; + +namespace BMA.EHR.Insignia.Service.Services +{ + public class BackgroundTaskQueue : IBackgroundTaskQueue + { + private readonly ConcurrentQueue> _workItems = new(); + private readonly SemaphoreSlim _signal = new(0); + + public ValueTask QueueBackgroundWorkItemAsync(Func workItem) + { + if (workItem == null) + throw new ArgumentNullException(nameof(workItem)); + _workItems.Enqueue(workItem); + _signal.Release(); + return ValueTask.CompletedTask; + } + + public async ValueTask> DequeueAsync(CancellationToken cancellationToken) + { + await _signal.WaitAsync(cancellationToken); + _workItems.TryDequeue(out var workItem); + return workItem!; + } + } +} diff --git a/BMA.EHR.Insignia/Services/IBackgroundTaskQueue.cs b/BMA.EHR.Insignia/Services/IBackgroundTaskQueue.cs new file mode 100644 index 00000000..7259a5fb --- /dev/null +++ b/BMA.EHR.Insignia/Services/IBackgroundTaskQueue.cs @@ -0,0 +1,8 @@ +namespace BMA.EHR.Insignia.Service.Services +{ + public interface IBackgroundTaskQueue + { + ValueTask QueueBackgroundWorkItemAsync(Func workItem); + ValueTask> DequeueAsync(CancellationToken cancellationToken); + } +} diff --git a/BMA.EHR.Insignia/Services/InsigniaRequestProcessService.cs b/BMA.EHR.Insignia/Services/InsigniaRequestProcessService.cs new file mode 100644 index 00000000..2894a375 --- /dev/null +++ b/BMA.EHR.Insignia/Services/InsigniaRequestProcessService.cs @@ -0,0 +1,153 @@ +using Microsoft.Extensions.Hosting; +using Quobject.SocketIoClientDotNet.Client; +using Sentry; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System; + +namespace BMA.EHR.Insignia.Service.Services; + +public class InsigniaRequestProcessService : BackgroundService +{ + private readonly IBackgroundTaskQueue _queue; + private Socket _socket; + private bool _isConnected = false; + + public InsigniaRequestProcessService(IBackgroundTaskQueue queue) + { + _queue = queue; + } + + public override Task StartAsync(CancellationToken cancellationToken) + { + try + { + _socket = IO.Socket("https://bma-ehr.frappet.synology.me", new IO.Options + { + Path = "/api/v1/org-socket", + //Query = new Dictionary + //{ + // { "EIO", "4" }, + // { "transport", "polling" }, + // { "t", "tkitfptn" } + //} + }); + + _socket.On(Socket.EVENT_CONNECT, () => + { + _isConnected = true; + Console.WriteLine("Connected to WebSocket server at: https://bma-ehr.frappet.synology.me/api/v1/org-socket"); + }); + + _socket.On(Socket.EVENT_DISCONNECT, () => + { + _isConnected = false; + Console.WriteLine("Disconnected from WebSocket server"); + }); + + _socket.On(Socket.EVENT_ERROR, (data) => + { + _isConnected = false; + Console.WriteLine($"WebSocket error: {data}"); + }); + + _socket.Connect(); + } + catch (Exception ex) + { + _isConnected = false; + Console.WriteLine($"Failed to initialize WebSocket connection: {ex.Message}"); + } + + return base.StartAsync(cancellationToken); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var userId = "4064c2b2-0414-464a-97c6-4a47c325b9a3"; + + while (!stoppingToken.IsCancellationRequested) + { + try + { + var workItem = await _queue.DequeueAsync(stoppingToken); + if (workItem != null) + { + Console.WriteLine($"Starting background task at: {DateTime.Now}"); + await workItem(stoppingToken); + Console.WriteLine($"Finished background task at: {DateTime.Now}"); + + // Send notification to WebSocket after task completion + if (_socket != null && _isConnected) + { + _socket.Emit("send-command-notification", + new + { + success = true, + message = "Background Task Completed Successfully", + payload = new { + completedAt = DateTime.Now, + taskType = "background_processing" + } + }, + new + { + userId = userId + }); + + Console.WriteLine("WebSocket notification sent successfully"); + } + else + { + Console.WriteLine("WebSocket is not connected. Unable to send notification."); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error processing background task: {ex.Message}"); + + // Send error notification to WebSocket + if (_socket != null && _isConnected) + { + _socket.Emit("send-command-notification", + new + { + success = false, + message = "Background Task Failed", + payload = new { + error = ex.Message, + failedAt = DateTime.Now, + taskType = "background_processing" + } + }, + new + { + userId = userId + }); + } + } + } + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + try + { + if (_socket != null) + { + Console.WriteLine("Disconnecting from WebSocket server..."); + _socket.Disconnect(); + _isConnected = false; + _socket = null; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error disconnecting WebSocket: {ex.Message}"); + } + + return base.StopAsync(cancellationToken); + } +}