/setup yönlendirmesi
This commit is contained in:
parent
20cbd50dc8
commit
f6ee88baf3
7 changed files with 127 additions and 41 deletions
10
README.md
10
README.md
|
|
@ -225,10 +225,16 @@ dotnet run --project src/Sozsoft.Platform.HttpApi.Host
|
|||
|
||||
Uygulama `https://localhost:44344` adresinde açılır. Swagger UI kök adreste yayınlanır.
|
||||
|
||||
**Veritabanı ilk kurulum:** Veritabanı yoksa host, tam ABP yığını yerine minimal bir
|
||||
**Veritabanı ilk kurulum:** Açılışta veritabanı yoksa host, tam ABP yığını yerine minimal bir
|
||||
"setup" uygulaması ile açılır (`DbStartup/SetupAppRunner`). Bu durumda:
|
||||
|
||||
- `GET /api/setup/application-status` — veritabanının hazır olup olmadığını döner (anonim).
|
||||
`setupMode: true` ise UI `/setup`'a yönlendirir; katalog ya da çekirdek şema yokken tam yığın da
|
||||
bunu döner (`DatabaseReadiness.RequiresSetup`), bağlantı hatası ise bakım ekranında kalır.
|
||||
- Veritabanı tam yığın çalışırken silinirse süreç kurulum moduna geçmez; `DatabaseReadinessGate`
|
||||
kurulum dışı her isteğe 503 döner, `/api/setup/*` isteklerini veritabanına dokunan ara katmanları
|
||||
(dil, tenant, oturum) atlayarak geçirir ve Authorization başlığını düşürür — migration yalnızca
|
||||
kurulum parolasıyla çalışır, bitince uygulama kendini durdurur (process yöneticisi yeniden başlatır).
|
||||
- UI tarafında `/setup` route'u (`views/setup/DatabaseSetup.tsx`) migration'ı tetikleyen ekranı gösterir.
|
||||
- Migration için `Setup:MigrationPassword` yapılandırması veya `App.Setup.Migrate` yetkisi gerekir.
|
||||
- Ekran DbMigrator'ü `SetupMigrationRunner` ile çalıştırır. `Setup:MigratorPath` publish çıktısını
|
||||
|
|
@ -2233,7 +2239,7 @@ configs/seeds/
|
|||
|
||||
| Belirti | Olası neden ve çözüm |
|
||||
| --- | --- |
|
||||
| `/api/abp/application-configuration` 503 dönüyor | Veritabanına erişilemiyor. Bağlantı dizesini ve veri konteynerini kontrol edin; `/api/setup/application-status` ile durumu görün. |
|
||||
| `/api/abp/application-configuration` (ve kurulum dışı her uç) 503 dönüyor | Veritabanına erişilemiyor (`DatabaseReadinessGate`). Bağlantı dizesini ve veri konteynerini kontrol edin; `/api/setup/application-status` ile durumu görün. |
|
||||
| UI `/setup` ekranına düşüyor | Şema oluşmamış veya bekleyen migration var. Setup ekranından migration'ı tetikleyin ya da DbMigrator'ı çalıştırın. |
|
||||
| Giriş "UserCannotSignIn..." hatası veriyor | [11.3](#113-giriş-engelleri) tablosuna bakın: IP, çalışma saati, doğrulama, şube/eş zamanlı kullanıcı limiti. |
|
||||
| Yeni sürüm tarayıcıda görünmüyor | Yeni worker indirmeyi bitirince kendiliğinden devreye girer ve sayfa yenilenir (`swRegistration.ts`). Kurulum takıldıysa sekmeyi tamamen kapatıp açın ya da DevTools › Application › Service Workers'tan eski worker'ı kaldırın. |
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ namespace Sozsoft.Platform.Controllers;
|
|||
/// Canlı sistemde (tam ABP stack ayaktayken) veritabanı durumunu raporlar ve
|
||||
/// yetkili host kullanıcısının migration tetiklemesine izin verir.
|
||||
///
|
||||
/// Veritabanı hiç yokken bu controller çalışmaz; o durumda
|
||||
/// <see cref="DbStartup.SetupAppRunner"/> içindeki minimal uygulama devrededir.
|
||||
/// Açılışta veritabanı yoksa bu controller çalışmaz; o durumda
|
||||
/// <see cref="DbStartup.SetupAppRunner"/> içindeki minimal uygulama devrededir. Veritabanı tam stack
|
||||
/// çalışırken kaybolursa istekler <see cref="DatabaseReadinessGate"/> üzerinden yine buraya ulaşır.
|
||||
/// İki yol da <see cref="SetupMigrationRunner"/> üzerinden aynı migration akışını kullanır.
|
||||
/// </summary>
|
||||
[Route("api/setup")]
|
||||
|
|
@ -122,7 +123,11 @@ public class SetupController : ControllerBase
|
|||
"Database migration triggered via /api/setup/migrate by user {UserId}.",
|
||||
User?.Identity?.Name ?? "(setup password)");
|
||||
|
||||
await RunMigratorAsync(environment: null, reloadWorkers: true);
|
||||
// Katalog yokken kurulan veritabanı yeni kimliklerle gelir; çalışan stack'in cache'leri,
|
||||
// Hangfire ve OpenIddict depoları eski veritabanına aittir. Minimal kurulum uygulamasındaki
|
||||
// gibi süreç durur, process yöneticisi yeniden başlatır.
|
||||
var restartAfterMigration = DatabaseReadinessChecker.GetStatus(_configuration).RequiresSetup;
|
||||
await RunMigratorAsync(environment: null, reloadWorkers: !restartAfterMigration, restartAfterMigration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -170,8 +175,12 @@ public class SetupController : ControllerBase
|
|||
/// DbMigrator'ü tek kilit altında çalıştırır; migrate ile tenant seed aynı anda yürümez.
|
||||
/// Worker tanımları host verisidir; tek tenant seed'i onlara dokunmadığı için
|
||||
/// <paramref name="reloadWorkers"/> yalnızca tam migrate'te açıktır.
|
||||
/// <paramref name="restartAfterMigration"/> açıkken worker tazelenmez, uygulama kendini durdurur.
|
||||
/// </summary>
|
||||
private async Task RunMigratorAsync(IReadOnlyDictionary<string, string>? environment, bool reloadWorkers)
|
||||
private async Task RunMigratorAsync(
|
||||
IReadOnlyDictionary<string, string>? environment,
|
||||
bool reloadWorkers,
|
||||
bool restartAfterMigration = false)
|
||||
{
|
||||
var stream = new SetupEventStream(Response);
|
||||
await stream.StartAsync();
|
||||
|
|
@ -199,6 +208,18 @@ public class SetupController : ControllerBase
|
|||
return;
|
||||
}
|
||||
|
||||
if (restartAfterMigration)
|
||||
{
|
||||
await stream.SuccessAsync("Migration and seed completed successfully.");
|
||||
await stream.RestartAsync("Application server is restarting...");
|
||||
await stream.DoneAsync("Completed.");
|
||||
|
||||
// İstemcinin "restart" olayını işleyip poll'a geçmesi için kısa gecikme.
|
||||
_ = Task.Delay(TimeSpan.FromSeconds(1.5))
|
||||
.ContinueWith(_ => _lifetime.StopApplication(), TaskScheduler.Default);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reloadWorkers)
|
||||
{
|
||||
await stream.SuccessAsync("Migration and seed completed successfully.");
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ public sealed record DatabaseReadiness(
|
|||
/// <summary>Uygulamanın tam ABP stack ile açılabilmesi için yeterli mi.</summary>
|
||||
public bool IsReady => DatabaseExists && SchemaReady;
|
||||
|
||||
/// <summary>
|
||||
/// Kurulum gerekiyor mu: sunucuya ulaşıldı ama katalog ya da çekirdek şema yok. Bağlantı/oturum
|
||||
/// hatası (<see cref="Error"/> dolu) geçici sayılır ve kurulum değildir; o durumda istemci
|
||||
/// bakım ekranında kalır.
|
||||
/// </summary>
|
||||
public bool RequiresSetup => !IsReady && Error is null;
|
||||
|
||||
public static DatabaseReadiness NotReady(string? error)
|
||||
=> new(false, false, null, error);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Sozsoft.Platform.DbStartup;
|
||||
|
||||
/// <summary>
|
||||
/// Veritabanı hazır değilken boru hattının kapısı.
|
||||
///
|
||||
/// Kurulum moduna yalnızca açılışta karar verilir (<see cref="Program"/>); tam ABP stack
|
||||
/// çalışırken veritabanı kaybolursa (katalog düşürüldü, sunucu erişilemez) süreç kurulum moduna
|
||||
/// geçmez. Bu durumda istek yolundaki dil sağlayıcısı, tenant çözümleyici ve oturum doğrulaması
|
||||
/// veritabanına gidip 500 ile düşüyordu — kurulumu yapacak <c>/api/setup/*</c> uçları da dahil.
|
||||
///
|
||||
/// - Kurulum dışı her isteğe anında 503 döner; EF sorguları ve bağlantı zaman aşımı devreye girmez.
|
||||
/// - <c>/api/setup/*</c> isteklerini geçirir ama veritabanına dokunan ara katmanların atlanması için
|
||||
/// işaretler (<see cref="IsDatabaseBypassed"/>). Oturum veritabanı olmadan doğrulanamayacağı için
|
||||
/// Authorization başlığı düşürülür; migration bu durumda yalnızca kurulum parolasıyla çalışır
|
||||
/// (<see cref="Controllers.SetupController"/>).
|
||||
///
|
||||
/// Kontrol cache'lidir (<see cref="DatabaseReadinessChecker"/>), istek başına maliyeti yoktur.
|
||||
/// </summary>
|
||||
public sealed class DatabaseReadinessGate
|
||||
{
|
||||
public const string SetupPathPrefix = "/api/setup";
|
||||
|
||||
private const string BypassItemKey = "Sozsoft.DatabaseReadinessGate.Bypass";
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public DatabaseReadinessGate(RequestDelegate next, IConfiguration configuration)
|
||||
{
|
||||
_next = next;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
if (DatabaseReadinessChecker.GetStatus(_configuration).IsReady)
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Request.Path.StartsWithSegments(SetupPathPrefix))
|
||||
{
|
||||
context.Items[BypassItemKey] = true;
|
||||
context.Request.Headers.Remove(HeaderNames.Authorization);
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
context.Response.ContentType = "text/plain; charset=utf-8";
|
||||
await context.Response.WriteAsync("The database is not reachable.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bu istekte veritabanına dokunan ara katmanlar atlanmalı mı (veritabanı hazır değilken
|
||||
/// geçirilen kurulum isteği).
|
||||
/// </summary>
|
||||
public static bool IsDatabaseBypassed(HttpContext context)
|
||||
=> context.Items.ContainsKey(BypassItemKey);
|
||||
}
|
||||
|
|
@ -12,11 +12,12 @@ public sealed class SetupStatusDto
|
|||
public bool DbExists { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Sunucu kurulum modunda mı (minimal uygulama) çalışıyor.
|
||||
/// Kurulum ekranı gösterilmeli mi. Frontend yalnızca bu değer <c>true</c> iken /setup
|
||||
/// sayfasına yönlendirir.
|
||||
///
|
||||
/// Frontend yalnızca bu değer <c>true</c> iken /setup sayfasına yönlendirir. Tam ABP stack
|
||||
/// ayaktayken bu değer her zaman <c>false</c>'tur; böylece canlı sistemde geçici bir
|
||||
/// veritabanı sorunu anonim kullanıcıyı kurulum ekranına düşürmez.
|
||||
/// Minimal kurulum uygulamasında her zaman <c>true</c>. Tam ABP stack ayaktayken yalnızca
|
||||
/// katalog/şema yokken (<see cref="DatabaseReadiness.RequiresSetup"/>) <c>true</c>; bağlantı
|
||||
/// hatası gibi geçici bir sorun anonim kullanıcıyı kurulum ekranına düşürmez.
|
||||
/// </summary>
|
||||
public bool SetupMode { get; init; }
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ public sealed class SetupStatusDto
|
|||
return new SetupStatusDto
|
||||
{
|
||||
DbExists = readiness.IsReady,
|
||||
SetupMode = setupMode,
|
||||
SetupMode = setupMode || readiness.RequiresSetup,
|
||||
PendingMigrationCount = includeDiagnostics ? readiness.PendingMigrationCount : null,
|
||||
// Hata detayı bağlantı dizesi/sunucu adı sızdırabilir — sadece yetkiliye.
|
||||
Error = includeDiagnostics ? readiness.Error : null,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ namespace Sozsoft.Platform.Identity;
|
|||
|
||||
public class PlatformSessionValidationMiddleware : IMiddleware, ITransientDependency
|
||||
{
|
||||
private const string SetupPathPrefix = "/api/setup";
|
||||
private const string SetupStatusPath = "/api/setup/application-status";
|
||||
private const string SetupStatusPath = DatabaseReadinessGate.SetupPathPrefix + "/application-status";
|
||||
|
||||
private readonly IIdentitySessionRepository sessionRepository;
|
||||
private readonly ICurrentTenant currentTenant;
|
||||
|
|
@ -101,7 +100,7 @@ public class PlatformSessionValidationMiddleware : IMiddleware, ITransientDepend
|
|||
{
|
||||
var path = context.Request.Path;
|
||||
|
||||
if (!path.StartsWithSegments(SetupPathPrefix))
|
||||
if (!path.StartsWithSegments(DatabaseReadinessGate.SetupPathPrefix))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
|
|
@ -471,7 +470,12 @@ public class PlatformHttpApiHostModule : AbpModule
|
|||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseAbpRequestLocalization();
|
||||
// Veritabanı hazır değilken kurulum dışı istekler burada 503 ile kesilir; kurulum istekleri
|
||||
// veritabanına dokunan ara katmanları (dil, tenant) atlayarak SetupController'a ulaşır.
|
||||
app.UseMiddleware<DatabaseReadinessGate>();
|
||||
app.UseWhen(
|
||||
httpContext => !DatabaseReadinessGate.IsDatabaseBypassed(httpContext),
|
||||
branch => branch.UseAbpRequestLocalization());
|
||||
|
||||
// Auth hataları MVC tarafından /Error sayfasına yönlendirilmesin diye bu kaldırıldı
|
||||
// if (!env.IsDevelopment())
|
||||
|
|
@ -483,29 +487,6 @@ public class PlatformHttpApiHostModule : AbpModule
|
|||
app.UseResponseCompression();
|
||||
app.MapAbpStaticAssets();
|
||||
app.UseRouting();
|
||||
// Not: /api/setup/application-status artık SetupController tarafından karşılanıyor.
|
||||
// Buradaki kopya middleware kaldırıldı — durum mantığı tek yerde (DatabaseReadinessChecker).
|
||||
|
||||
// Veritabanı erişilemezken uygulama konfigürasyonu üretilemez. İsteği burada,
|
||||
// EF sorguları ve bağlantı zaman aşımları devreye girmeden kesiyoruz: aksi halde
|
||||
// istemci 10+ saniye bekleyip, contributor'ın içinde patlayan bir bağlantı
|
||||
// hatasından türeyen belirsiz bir yanıt alıyordu. Kontrol cache'li, maliyeti yok.
|
||||
app.Use(async (httpContext, next) =>
|
||||
{
|
||||
if (httpContext.Request.Path.StartsWithSegments("/api/abp/application-configuration"))
|
||||
{
|
||||
var configuration = httpContext.RequestServices.GetRequiredService<IConfiguration>();
|
||||
if (!DatabaseReadinessChecker.GetStatus(configuration).IsReady)
|
||||
{
|
||||
httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
httpContext.Response.ContentType = "text/plain; charset=utf-8";
|
||||
await httpContext.Response.WriteAsync("The database is not reachable.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
|
|
@ -528,8 +509,13 @@ public class PlatformHttpApiHostModule : AbpModule
|
|||
|
||||
if (PlatformConsts.IsMultiTenant)
|
||||
{
|
||||
app.UseMultiTenancy();
|
||||
app.UseMiddleware<TenantLocalizationMiddleware>();
|
||||
app.UseWhen(
|
||||
httpContext => !DatabaseReadinessGate.IsDatabaseBypassed(httpContext),
|
||||
branch =>
|
||||
{
|
||||
branch.UseMultiTenancy();
|
||||
branch.UseMiddleware<TenantLocalizationMiddleware>();
|
||||
});
|
||||
}
|
||||
|
||||
app.UseUnitOfWork();
|
||||
|
|
|
|||
Loading…
Reference in a new issue