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
# ตั้งค่า 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

View file

@ -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<ConfigureSwaggerOptions>();
builder.Services.AddSwaggerGen();
builder.Services.ConfigureOptions<ConfigureSwaggerOptions>();
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<IApiVersionDescriptionProvider>();
if (app.Environment.IsDevelopment())
{
var apiVersionDescriptionProvider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
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<ErrorHandlerMiddleware>();
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<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()
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");