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