add timezone in docker container and force before hangfire start

This commit is contained in:
Suphonchai Phoonsawat 2025-03-08 10:53:24 +07:00
parent 7ba9a6df5a
commit 772c9482aa
2 changed files with 143 additions and 134 deletions

View file

@ -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 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 WORKDIR /app
EXPOSE 80 EXPOSE 80
EXPOSE 443 EXPOSE 443

View file

@ -24,163 +24,167 @@ using BMA.EHR.Application.Repositories.Leaves.TimeAttendants;
using BMA.EHR.Leave.Service.Extensions; using BMA.EHR.Leave.Service.Extensions;
var builder = WebApplication.CreateBuilder(args); 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"]; opt.DefaultApiVersion = new ApiVersion(1, 0);
var key = builder.Configuration["Jwt:Key"]; 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(); // Authorization
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
builder.Services.AddApiVersioning(opt => {
opt.SaveToken = true;
opt.RequireHttpsMetadata = false; //false for dev
opt.Authority = issuer;
opt.TokenValidationParameters = new()
{ {
opt.DefaultApiVersion = new ApiVersion(1, 0); ValidateIssuer = true,
opt.AssumeDefaultVersionWhenUnspecified = true; ValidateAudience = false,
opt.ReportApiVersions = true; ValidateLifetime = true,
opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), ValidateIssuerSigningKey = true,
new HeaderApiVersionReader("x-api-version"), ValidIssuer = issuer,
new MediaTypeApiVersionReader("x-api-version")); IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
}); };
});
builder.Services.AddAuthorization();
builder.Services.AddVersionedApiExplorer(setup => // use serilog
{ ConfigureLogs();
setup.GroupNameFormat = "'v'VVV"; builder.Host.UseSerilog();
setup.SubstituteApiVersionInUrl = true;
});
builder.Services.AddEndpointsApiExplorer(); // Add config CORS
builder.Services.AddCors(options => options.AddDefaultPolicy(builder =>
// Authorization {
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt => builder
{ .AllowAnyOrigin()
opt.SaveToken = true; .AllowAnyMethod()
opt.RequireHttpsMetadata = false; //false for dev .AllowAnyHeader()
opt.Authority = issuer; .SetIsOriginAllowedToAllowWildcardSubdomains();
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 services to the container. // Add services to the container.
builder.Services.AddApplication(); builder.Services.AddApplication();
builder.Services.AddLeaveApplication(); builder.Services.AddLeaveApplication();
builder.Services.AddPersistence(builder.Configuration); builder.Services.AddPersistence(builder.Configuration);
builder.Services.AddLeavePersistence(builder.Configuration); builder.Services.AddLeavePersistence(builder.Configuration);
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
builder.Services.AddControllers(options => builder.Services.AddControllers(options =>
{ {
options.SuppressAsyncSuffixInActionNames = false; options.SuppressAsyncSuffixInActionNames = false;
}) })
.AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
builder.Services.ConfigureOptions<ConfigureSwaggerOptions>(); builder.Services.ConfigureOptions<ConfigureSwaggerOptions>();
builder.Services.AddHealthChecks(); builder.Services.AddHealthChecks();
builder.Services.AddRabbitMqConnectionPooling(builder.Configuration); builder.Services.AddRabbitMqConnectionPooling(builder.Configuration);
// Add Hangfire services. // Add Hangfire services.
var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection"); 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 app = builder.Build();
var apiVersionDescriptionProvider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
if (app.Environment.IsDevelopment())
{ {
var apiVersionDescriptionProvider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>(); app.UseSwagger();
app.UseSwaggerUI(options =>
if (app.Environment.IsDevelopment())
{ {
app.UseSwagger(); foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
app.UseSwaggerUI(options =>
{ {
foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
{ description.GroupName.ToUpperInvariant());
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<ErrorHandlerMiddleware>();
app.UseHangfireDashboard("/hangfire", new DashboardOptions()
{
Authorization = new[] { new CustomAuthorizeFilter() }
}); });
var manager = new RecurringJobManager();
if (manager != null)
{
manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression<UserDutyTimeRepository>(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<LeaveDbContext>();
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<ErrorHandlerMiddleware>();
app.UseHangfireDashboard("/hangfire", new DashboardOptions()
{
Authorization = new[] { new CustomAuthorizeFilter() }
});
var manager = new RecurringJobManager();
if (manager != null)
{
manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression<UserDutyTimeRepository>(x => x.UpdateUserDutyTime()), "0 1 * * *", bangkokTimeZone);
}
// apply migrations
await using var scope = app.Services.CreateAsyncScope();
await using var db = scope.ServiceProvider.GetRequiredService<LeaveDbContext>();
await db.Database.MigrateAsync();
// seed default data
await LeaveSeeder.SeedLeaveType(app);
app.Run();
void ConfigureLogs() void ConfigureLogs()
{ {
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");