refactor: 禁止 TenantId=0 并改为系统租户
This commit is contained in:
@@ -327,7 +327,7 @@ public sealed class DictionaryAppService(
|
|||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
if (scope == DictionaryScope.System)
|
if (scope == DictionaryScope.System)
|
||||||
{
|
{
|
||||||
EnsurePlatformTenant(tenantId);
|
EnsureSystemTenant(tenantId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,9 +362,9 @@ public sealed class DictionaryAppService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsurePlatformTenant(long tenantId)
|
private void EnsureSystemTenant(long tenantId)
|
||||||
{
|
{
|
||||||
// 1. (空行后) 系统字典只能在平台租户(TenantId=0)上下文中操作
|
// 1. (空行后) 系统字典只能在系统租户(TenantId=0)上下文中操作
|
||||||
if (tenantId != 0)
|
if (tenantId != 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Forbidden, "租户端不允许操作系统字典");
|
throw new BusinessException(ErrorCodes.Forbidden, "租户端不允许操作系统字典");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using TakeoutSaaS.Shared.Abstractions.Entities;
|
|||||||
namespace TakeoutSaaS.Domain.Tenants.Entities;
|
namespace TakeoutSaaS.Domain.Tenants.Entities;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 平台租户信息,描述租户的生命周期与基础资料。
|
/// 租户信息,描述租户的生命周期与基础资料。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Tenant : AuditableEntityBase
|
public sealed class Tenant : AuditableEntityBase
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public sealed class AppDataSeeder(
|
|||||||
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
|
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
|
||||||
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
|
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
|
||||||
|
|
||||||
await EnsurePlatformTenantAsync(appDbContext, cancellationToken);
|
await EnsureSystemTenantAsync(appDbContext, cancellationToken);
|
||||||
var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken);
|
var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken);
|
||||||
await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken);
|
await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken);
|
||||||
|
|
||||||
@@ -132,9 +132,9 @@ public sealed class AppDataSeeder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 确保平台租户存在。
|
/// 确保系统租户存在(TenantId=0,用于系统级数据归属)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task EnsurePlatformTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
|
private async Task EnsureSystemTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var existingTenant = await dbContext.Tenants
|
var existingTenant = await dbContext.Tenants
|
||||||
.IgnoreQueryFilters()
|
.IgnoreQueryFilters()
|
||||||
@@ -142,20 +142,47 @@ public sealed class AppDataSeeder(
|
|||||||
|
|
||||||
if (existingTenant != null)
|
if (existingTenant != null)
|
||||||
{
|
{
|
||||||
|
// 1. (空行后) 若历史数据仍为 PLATFORM,则自动修正为 SYSTEM
|
||||||
|
var updated = false;
|
||||||
|
if (!string.Equals(existingTenant.Code, "SYSTEM", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
existingTenant.Code = "SYSTEM";
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(existingTenant.Name, "System", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
existingTenant.Name = "System";
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingTenant.Status != TenantStatus.Active)
|
||||||
|
{
|
||||||
|
existingTenant.Status = TenantStatus.Active;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated)
|
||||||
|
{
|
||||||
|
dbContext.Tenants.Update(existingTenant);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
logger.LogInformation("AppSeed 已更新系统租户 SYSTEM");
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenant = new Tenant
|
var tenant = new Tenant
|
||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Code = "PLATFORM",
|
Code = "SYSTEM",
|
||||||
Name = "Platform",
|
Name = "System",
|
||||||
Status = TenantStatus.Active
|
Status = TenantStatus.Active
|
||||||
};
|
};
|
||||||
|
|
||||||
await dbContext.Tenants.AddAsync(tenant, cancellationToken);
|
await dbContext.Tenants.AddAsync(tenant, cancellationToken);
|
||||||
await dbContext.SaveChangesAsync(cancellationToken);
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
logger.LogInformation("AppSeed 已创建平台租户 PLATFORM");
|
logger.LogInformation("AppSeed 已创建系统租户 SYSTEM");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -89,18 +89,25 @@ public sealed class TenantResolutionMiddleware(
|
|||||||
private static TenantContext ResolveTenant(HttpContext context, TenantResolutionOptions options)
|
private static TenantContext ResolveTenant(HttpContext context, TenantResolutionOptions options)
|
||||||
{
|
{
|
||||||
var request = context.Request;
|
var request = context.Request;
|
||||||
|
var isAuthenticated = context.User?.Identity?.IsAuthenticated == true;
|
||||||
|
|
||||||
// 1. Token Claim(已认证请求必须以 Claim 为准,避免 Header 覆盖导致跨租户访问)
|
// 1. Token Claim(已认证请求必须以 Claim 为准,避免 Header 覆盖导致跨租户访问)
|
||||||
var claim = context.User?.FindFirst("tenant_id");
|
var claim = context.User?.FindFirst("tenant_id");
|
||||||
if (claim != null && long.TryParse(claim.Value, out var claimTenant))
|
if (claim != null && long.TryParse(claim.Value, out var claimTenant) && claimTenant > 0)
|
||||||
{
|
{
|
||||||
return new TenantContext(claimTenant, null, "claim:tenant_id");
|
return new TenantContext(claimTenant, null, "claim:tenant_id");
|
||||||
}
|
}
|
||||||
|
// 1.1 (空行后) 已认证但缺少合法租户 Claim,则视为未解析(不允许 Header 覆盖)
|
||||||
|
if (isAuthenticated)
|
||||||
|
{
|
||||||
|
return TenantContext.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Header 中的租户 ID
|
// 2. Header 中的租户 ID
|
||||||
if (!string.IsNullOrWhiteSpace(options.TenantIdHeaderName) &&
|
if (!string.IsNullOrWhiteSpace(options.TenantIdHeaderName) &&
|
||||||
request.Headers.TryGetValue(options.TenantIdHeaderName, out var tenantHeader) &&
|
request.Headers.TryGetValue(options.TenantIdHeaderName, out var tenantHeader) &&
|
||||||
long.TryParse(tenantHeader.FirstOrDefault(), out var headerTenantId))
|
long.TryParse(tenantHeader.FirstOrDefault(), out var headerTenantId) &&
|
||||||
|
headerTenantId > 0)
|
||||||
{
|
{
|
||||||
return new TenantContext(headerTenantId, null, $"header:{options.TenantIdHeaderName}");
|
return new TenantContext(headerTenantId, null, $"header:{options.TenantIdHeaderName}");
|
||||||
}
|
}
|
||||||
@@ -120,7 +127,7 @@ public sealed class TenantResolutionMiddleware(
|
|||||||
var host = request.Host.Host;
|
var host = request.Host.Host;
|
||||||
if (!string.IsNullOrWhiteSpace(host))
|
if (!string.IsNullOrWhiteSpace(host))
|
||||||
{
|
{
|
||||||
if (options.DomainTenantMap.TryGetValue(host, out var tenantFromHost))
|
if (options.DomainTenantMap.TryGetValue(host, out var tenantFromHost) && tenantFromHost > 0)
|
||||||
{
|
{
|
||||||
return new TenantContext(tenantFromHost, null, $"host:{host}");
|
return new TenantContext(tenantFromHost, null, $"host:{host}");
|
||||||
}
|
}
|
||||||
@@ -143,7 +150,7 @@ public sealed class TenantResolutionMiddleware(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return options.CodeTenantMap.TryGetValue(code, out tenantId);
|
return options.CodeTenantMap.TryGetValue(code, out tenantId) && tenantId > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? ResolveCodeFromHost(string host, string? rootDomain)
|
private static string? ResolveCodeFromHost(string host, string? rootDomain)
|
||||||
|
|||||||
Reference in New Issue
Block a user