From 772c9482aa3202435806eaabd1fd4c30fa5da2b8 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Sat, 8 Mar 2025 10:53:24 +0700 Subject: [PATCH] add timezone in docker container and force before hangfire start --- BMA.EHR.Leave/Dockerfile | 7 +- BMA.EHR.Leave/Program.cs | 270 ++++++++++++++++++++------------------- 2 files changed, 143 insertions(+), 134 deletions(-) diff --git a/BMA.EHR.Leave/Dockerfile b/BMA.EHR.Leave/Dockerfile index 3c211ab3..2e851b2c 100644 --- a/BMA.EHR.Leave/Dockerfile +++ b/BMA.EHR.Leave/Dockerfile @@ -1,6 +1,11 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base + +# ตั้งค่า TimeZone ใน Container +ENV TZ=Asia/Bangkok +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + WORKDIR /app EXPOSE 80 EXPOSE 443 diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 3415f8f5..82ae3aea 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -24,163 +24,167 @@ using BMA.EHR.Application.Repositories.Leaves.TimeAttendants; using BMA.EHR.Leave.Service.Extensions; var builder = WebApplication.CreateBuilder(args); +// ตั้ง TimeZone เป็น Asia/Bangkok ในโค้ด +var bangkokTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Bangkok"); +TimeZoneInfo.ClearCachedData(); + + +var issuer = builder.Configuration["Jwt:Issuer"]; +var key = builder.Configuration["Jwt:Key"]; + + +IdentityModelEventSource.ShowPII = true; + +builder.Services.AddHttpContextAccessor(); + +builder.Services.AddApiVersioning(opt => { - var issuer = builder.Configuration["Jwt:Issuer"]; - var key = builder.Configuration["Jwt:Key"]; + opt.DefaultApiVersion = new ApiVersion(1, 0); + opt.AssumeDefaultVersionWhenUnspecified = true; + opt.ReportApiVersions = true; + opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), + new HeaderApiVersionReader("x-api-version"), + new MediaTypeApiVersionReader("x-api-version")); +}); +builder.Services.AddVersionedApiExplorer(setup => +{ + setup.GroupNameFormat = "'v'VVV"; + setup.SubstituteApiVersionInUrl = true; +}); - IdentityModelEventSource.ShowPII = true; +builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddHttpContextAccessor(); - - builder.Services.AddApiVersioning(opt => +// Authorization +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt => +{ + opt.SaveToken = true; + opt.RequireHttpsMetadata = false; //false for dev + opt.Authority = issuer; + opt.TokenValidationParameters = new() { - opt.DefaultApiVersion = new ApiVersion(1, 0); - opt.AssumeDefaultVersionWhenUnspecified = true; - opt.ReportApiVersions = true; - opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), - new HeaderApiVersionReader("x-api-version"), - new MediaTypeApiVersionReader("x-api-version")); - }); + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = issuer, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) + }; +}); +builder.Services.AddAuthorization(); - builder.Services.AddVersionedApiExplorer(setup => - { - setup.GroupNameFormat = "'v'VVV"; - setup.SubstituteApiVersionInUrl = true; - }); +// use serilog +ConfigureLogs(); +builder.Host.UseSerilog(); - builder.Services.AddEndpointsApiExplorer(); - - // Authorization - builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt => - { - opt.SaveToken = true; - opt.RequireHttpsMetadata = false; //false for dev - opt.Authority = issuer; - opt.TokenValidationParameters = new() - { - ValidateIssuer = true, - ValidateAudience = false, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuer = issuer, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) - }; - }); - builder.Services.AddAuthorization(); - - // use serilog - ConfigureLogs(); - builder.Host.UseSerilog(); - - // Add config CORS - builder.Services.AddCors(options => options.AddDefaultPolicy(builder => - { - builder - .AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader() - .SetIsOriginAllowedToAllowWildcardSubdomains(); - })); +// Add config CORS +builder.Services.AddCors(options => options.AddDefaultPolicy(builder => +{ + builder + .AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .SetIsOriginAllowedToAllowWildcardSubdomains(); +})); - // Add services to the container. - builder.Services.AddApplication(); - builder.Services.AddLeaveApplication(); - builder.Services.AddPersistence(builder.Configuration); - builder.Services.AddLeavePersistence(builder.Configuration); +// Add services to the container. +builder.Services.AddApplication(); +builder.Services.AddLeaveApplication(); +builder.Services.AddPersistence(builder.Configuration); +builder.Services.AddLeavePersistence(builder.Configuration); - builder.Services.AddHttpClient(); +builder.Services.AddHttpClient(); - builder.Services.AddControllers(options => - { - options.SuppressAsyncSuffixInActionNames = false; - }) - .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); +builder.Services.AddControllers(options => +{ + options.SuppressAsyncSuffixInActionNames = false; +}) +.AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); - builder.Services.AddSwaggerGen(); - builder.Services.ConfigureOptions(); +builder.Services.AddSwaggerGen(); +builder.Services.ConfigureOptions(); - builder.Services.AddHealthChecks(); +builder.Services.AddHealthChecks(); - builder.Services.AddRabbitMqConnectionPooling(builder.Configuration); +builder.Services.AddRabbitMqConnectionPooling(builder.Configuration); - // Add Hangfire services. - var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection"); +// Add Hangfire services. +var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection"); + +builder.Services.AddHangfire(configuration => configuration + .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseStorage( + new MySqlStorage( + defaultConnection, + new MySqlStorageOptions + { + TransactionIsolationLevel = IsolationLevel.ReadCommitted, + QueuePollInterval = TimeSpan.FromSeconds(15), + JobExpirationCheckInterval = TimeSpan.FromHours(1), + CountersAggregateInterval = TimeSpan.FromMinutes(5), + PrepareSchemaIfNecessary = true, + DashboardJobListLimit = 50000, + TransactionTimeout = TimeSpan.FromMinutes(1), + TablesPrefix = "Hangfire" + }))); +builder.Services.AddHangfireServer(); - builder.Services.AddHangfire(configuration => configuration - .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) - .UseSimpleAssemblyNameTypeSerializer() - .UseRecommendedSerializerSettings() - .UseStorage( - new MySqlStorage( - defaultConnection, - new MySqlStorageOptions - { - TransactionIsolationLevel = IsolationLevel.ReadCommitted, - QueuePollInterval = TimeSpan.FromSeconds(15), - JobExpirationCheckInterval = TimeSpan.FromHours(1), - CountersAggregateInterval = TimeSpan.FromMinutes(5), - PrepareSchemaIfNecessary = true, - DashboardJobListLimit = 50000, - TransactionTimeout = TimeSpan.FromMinutes(1), - TablesPrefix = "Hangfire" - }))); - builder.Services.AddHangfireServer(); -} var app = builder.Build(); + +var apiVersionDescriptionProvider = app.Services.GetRequiredService(); + +if (app.Environment.IsDevelopment()) { - var apiVersionDescriptionProvider = app.Services.GetRequiredService(); - - if (app.Environment.IsDevelopment()) + app.UseSwagger(); + app.UseSwaggerUI(options => { - app.UseSwagger(); - app.UseSwaggerUI(options => + foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) { - foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) - { - options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", - description.GroupName.ToUpperInvariant()); - } - }); - } - - app.MapHealthChecks("/health"); - - - app.UseHttpsRedirection(); - app.UseCors(); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseDefaultFiles(); - app.UseStaticFiles(); - app.MapControllers(); - app.UseMiddleware(); - - - app.UseHangfireDashboard("/hangfire", new DashboardOptions() - { - Authorization = new[] { new CustomAuthorizeFilter() } + options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", + description.GroupName.ToUpperInvariant()); + } }); - - var manager = new RecurringJobManager(); - if (manager != null) - { - manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", TimeZoneInfo.FindSystemTimeZoneById("Asia/Bangkok")); - } - - // apply migrations - await using var scope = app.Services.CreateAsyncScope(); - await using var db = scope.ServiceProvider.GetRequiredService(); - await db.Database.MigrateAsync(); - - // seed default data - await LeaveSeeder.SeedLeaveType(app); - - app.Run(); } +app.MapHealthChecks("/health"); + + +app.UseHttpsRedirection(); +app.UseCors(); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseDefaultFiles(); +app.UseStaticFiles(); +app.MapControllers(); +app.UseMiddleware(); + + +app.UseHangfireDashboard("/hangfire", new DashboardOptions() +{ + Authorization = new[] { new CustomAuthorizeFilter() } +}); + +var manager = new RecurringJobManager(); +if (manager != null) +{ + manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", bangkokTimeZone); +} + +// apply migrations +await using var scope = app.Services.CreateAsyncScope(); +await using var db = scope.ServiceProvider.GetRequiredService(); +await db.Database.MigrateAsync(); + +// seed default data +await LeaveSeeder.SeedLeaveType(app); + +app.Run(); + + void ConfigureLogs() { var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");